了解Data.Functor.Constant构造函数和应用法则

时间:2014-01-16 18:28:26

标签: haskell constants functor applicative monoids

我对Data.Functor.Constant的类型构造函数以及它如何与applicative一起使用感到困惑。


首先是构造函数:

当我检查Constant :: a -> Constant a b

的类型时

我看到它需要a,但会返回Constant a b

b来自哪里,为什么存在?


其次,我正在努力应用:

我理解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是同一个monoid时才有效,除非我使用纯粹的。所以我不确定为什么纯粹的工作方式不同。我怀疑这与b有关,这就是为什么他们在同一个问题上。

总结这两个问题:

  1. b在常量构造函数中做了什么?

  2. 为什么纯粹的工作即使内部的幺半群不同?

  3. 非常感谢!

1 个答案:

答案 0 :(得分:25)

好的,所以你有这种类型

data Const a b = Const { getConst :: a }

您的第一个问题是b来自何处?”

答案是它不是来自任何地方。以同样的方式,您可以将Maybe b视为包含类型b的0或1值的容器,Const a b是一个容器,它只包含0 {{1}类型的值1}}(但确实包含b类型的值。)

你的第二个问题是“为什么会出现?”

好吧,有时候让一个仿函数说它可能包含a类型的值,但实际上包含其他东西(例如想到b仿函数 - 有点不同之处在于{{} 1}} 可能持有Either a b类型的值,而Either a b肯定不会。)

然后您询问了代码段bConst a b。你认为这些是相同的,但它们不是。原因是pure id <*> Const "hello"的{​​{1}}实例看起来像

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)。

那么你的例子会发生什么?自[{1}}起,您必须m,其类型必须为Monoid。在这种情况下,幺半群是mempty。对于pure id <*> Const "hello"Const String a,我们有id :: a -> a。所以你最终得到了

String

另一方面,当您编写mempty = ""时,左侧参数的类型为String,右侧的类型为(<>) = (++),您会看到类型不匹配,这就是你得到类型错误的原因。

现在,为什么这个有用?一个应用程序位于lens库中,它允许您在纯函数设置中使用getter和setter(熟悉命令式编程)。镜头的简单定义是

pure id <*> Const "hello" = Const "" <*> Const "hello"
                          = Const ("" <> "hello")
                          = Const ("" ++ "hello")
                          = Const "hello"

即。如果你给它一个转换类型Const id <*> Const "hello"的值的函数,它将返回一个转换类型为Const (a -> a) b的值的函数。那有用的是什么?好吧,让我们为特定的仿函数Const String b选择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 } s的函数的函数。

我们可以传入的l类型的另一个函数是modify :: Lens b a -> (a -> a) -> (b -> b) modify l f = runIdentity . l (Identity . f) (请注意我们已经专门设置,以便第二种类型与第一种类型相同)。然后,镜头a的操作是将其转换为b类型的函数,它告诉我们它可能包含a -> f a,但实际上偷偷摸摸它确实包含Const :: a -> Const a a!一旦我们将其应用于l类型的某些内容以获得b -> Const a b,我们就可以使用b来点击它以将类型a的值拉出帽子。因此,这为我们提供了一种从b中提取Const a b类型值的方法 - 即它是一个吸气剂。该定义类似于

getConst :: Const a b -> a

作为镜头的一个例子,你可以定义

a

这样你就可以打开一个GHCI会话并写

a

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