好的,所以你有这种类型
data Const a b = Const { getConst :: a }
你的第一个问题是“b
从哪里来?”
答案是它不是来自任何地方。就像您可以将其Maybe b
视为一个包含 0 或 1 个 type 值的容器一样b
,aConst a b
是一个恰好包含 0 个 type 值的容器b
(但确实包含一个 type 值a
)。
你的第二个问题是“为什么会在那里?”
好吧,有时有一个函子说它可能包含 type 的值b
,但实际上包含其他东西是有用的(例如,想想Either a b
functor——不同之处在于它Either a b
可能包含 type的值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
实例为我们提供了一种从无到有(使用mempty
)创建一个值的方法。
那么在你的例子中发生了什么?你有pure id <*> Const "hello"
which must have type Const String a
since 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
时,您会看到类型不匹配,这就是您收到类型错误的原因。
现在,为什么这很有用?镜头库中有一个应用程序,它允许您在纯函数设置中使用 getter 和 setter(熟悉命令式编程)。镜头的简单定义是
type Lens b a = forall f. Functor f => (a -> f a) -> (b -> f b)
也就是说,如果你给它一个转换类型值的函数a
,它会给你一个转换类型值的函数b
。那有什么用?a -> f a
好吧,让我们为特定的 functor选择一个随机类型的函数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
(请注意,我们已经特化了第二个类型与第一个类型相同)。然后镜头的作用l
是把它变成一个类型的函数b -> Const a b
,它告诉我们它可能包含a b
,但实际上偷偷摸摸它确实包含一个a
!一旦我们将它应用于某种类型b
以获得 a Const a b
,我们就可以使用它来从帽子getConst :: Const a b -> a
中提取一个类型的值。a
因此,这为我们提供了一种a
从 a中提取类型值的方法b
——即它是一个 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 会话并编写
>> get first (1,2)
1
>> modify first (*2) (3,4)
(6,4)
正如您可能想象的那样,它在各种情况下都很有用。