我对 Data.Functor.Constant 的类型构造函数以及它如何与应用程序一起使用感到困惑。
首先是构造函数:
当我检查 Constant :: a -> Constant a b
的类型时
我看到它需要 a
, 但返回 Constant a b
b
在哪里来自,为什么存在?
其次,我在 Applicative 上苦苦挣扎:
我了解 Constant 需要在内部有一个 Monoid 才能成为 Applicative 实例。
它必须遵守的一条法律是:pure id <*> Constant x = x
我认为这与:Constant id <*> Constant x = x
相同
但我想我错了,因为下面的代码清楚地显示了纯粹的不同行为。
:t pure id <*> Constant "hello" // Constant [Char] b
:t Constant id <*> Constant "hello" // Couldn't match expected type `a0 -> a0' with actual type `[Char]'
:t pure id <*> Constant reverse // Constant ([a] -> [a]) b
:t Constant id <*> Constant reverse // Constant ([a] -> [a]) b
我看到它只有在
x
时才有效是同一个幺半群,除非我使用纯的。所以我不确定为什么 pure 的工作方式不同。我怀疑这与 b
有关这就是为什么他们在同一个问题上。总结一下这两个问题:
b
是什么意思在常量构造函数中做什么? 非常感谢!
最佳答案
好的,所以你有这种类型
data Const a b = Const { getConst :: a }
您的第一个问题是“
b
来自哪里?”答案是它不是来自任何地方。就像你可以想到
Maybe b
作为容纳 0 或 1 个 b
类型值的容器, 一个 Const a b
是一个容器,它恰好包含 b
类型的 0 个值(但确实拥有 a
类型的值)。你的第二个问题是“为什么会在那里?”
好吧,有时让一个仿函数说它可能包含
b
类型的值很有用。 ,但实际上包含其他东西(例如,想想 Either a b
仿函数——不同之处在于 Either a b
可能包含 b
类型的值,而 Const a b
绝对没有)。然后您询问了代码片段
pure id <*> Const "hello"
和 Const id <*> Const "hello"
.你以为这些是一样的,但事实并非如此。原因是 Applicative
Const
的实例好像instance Monoid m => Applicative (Const m) where
-- pure :: a -> Const m a
pure _ = Const mempty
-- <*> :: Const m (a -> b) -> Const m a -> Const m b
Const m1 <*> Const m2 = Const (m1 <> m2)
由于实际上没有任何值具有第二个参数的类型,我们只需要处理那些具有第一个参数类型的值,我们知道它是一个幺半群。这就是为什么我们可以制作
Const
Applicative
的一个实例-- 我们需要提取 m
类型的值从某个地方,和Monoid
instance 为我们提供了一种从无到有的方法(使用 mempty
)。那么在你的例子中发生了什么?你有
pure id <*> Const "hello"
必须有类型 Const String a
自从 id :: a -> a
.在这种情况下,幺半群是 String
.我们有mempty = ""
对于 String
, 和 (<>) = (++)
.所以你最终得到pure id <*> Const "hello" = Const "" <*> Const "hello"
= Const ("" <> "hello")
= Const ("" ++ "hello")
= Const "hello"
另一方面,当你写
Const id <*> Const "hello"
左侧参数的类型为 Const (a -> a) b
右边的类型为 Const String b
并且您看到类型不匹配,这就是您收到类型错误的原因。现在,为什么这很有用?一个应用程序位于 lens库,它允许您在纯函数设置中使用 getter 和 setter(熟悉命令式编程)。镜头的简单定义是
type Lens b a = forall f. Functor f => (a -> f a) -> (b -> f b)
即,如果你给它一个函数来转换
a
类型的值, 它会给你一个函数来转换 b
类型的值.那有什么用?好吧,让我们选择 a -> f a
类型的随机函数对于特定的仿函数f
.如果我们选择 Identity
仿函数,看起来像data Identity a = Identity { getIdentity :: a }
那么如果
l
是镜头,定义modify :: Lens b a -> (a -> a) -> (b -> b)
modify l f = runIdentity . l (Identity . f)
为您提供一种方法来获取转换
a
的函数s 并将它们转换为转换 b
的函数s。a -> f a
类型的另一个函数我们可以传入的是Const :: a -> Const a a
(请注意,我们已经专门化,以便第二种类型与第一种相同)。然后镜头的 Action l
就是把它变成b -> Const a b
类型的函数,这告诉我们它可能包含 b
, 但实际上它实际上包含一个 a
!一旦我们将它应用于 b
类型的东西为了得到 Const a b
, 我们可以用 getConst :: Const a b -> a
提取 a
类型的值从帽子里出来。因此,这为我们提供了一种提取 a
类型值的方法。来自 b
- 即它是一个 setter/getter 。定义看起来像get :: Lens b a -> b -> a
get l = getConst . l Const
作为一个镜头的例子,你可以定义
first :: Lens (a,b) a
first f (a,b) = fmap (\x -> (x,b)) (f a)
这样你就可以打开一个 GHCI session 并编写
>> get first (1,2)
1
>> modify first (*2) (3,4)
(6,4)
正如您可能想象的那样,它在各种情况下都很有用。
关于haskell - 理解 Data.Functor.Constant 构造函数和应用规律,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21169943/