15

我对 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他们在同一个问题中的原因有关。

总结一下这两个问题:

  1. 在 Constant 构造函数中做了什么b

  2. 为什么即使幺半群内部不同,pure 也会起作用?

非常感谢!

4

1 回答 1

27

好的,所以你有这种类型

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 bfunctor——不同之处在于它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 asince 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)

为您提供了一种方法来获取转换as 的函数并将它们转换为转换bs 的函数。

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)

正如您可能想象的那样,它在各种情况下都很有用。

于 2014-01-16T18:50:56.070 回答