《第一原理的Haskell编程》一书中有一个练习,要求我在Applicative
上实例化Constant a
。
我尝试过:
newtype Constant a b =
Constant { getConstant :: a }
deriving (Eq, Ord, Show)
instance Functor (Constant a) where
fmap f (Constant x) = Constant x
instance Monoid a => Applicative (Constant a) where
pure x = Constant { getConstant = x }
Constant { getConstant = x } <*> Constant { getConstant = y } =
Constant { getConstant = x <> y }
这不能编译。我潜入了Data.Functor.Constant
的{{3}},它具有以下定义:
pure _ = Constant mempty
即使在阅读本书中的“应用性”一章后,我还是不太了解Applicative
及其pure
功能。
我知道这可能是个大问题,但是有人可以解释一下pure的作用吗,为什么他们只定义pure
在mempty
上使用一些Monoid
?以及为什么这样:
pure x = Constant { getConstant = x }
不编译吗?我认为pure
只是将类型a
的值转换为类型f a
的值,在这个示例中f
只是Constant
。为什么与source code不同?
答案 0 :(得分:6)
您在这里遇到的主要麻烦是种类。我将尝试为您解释为什么您的代码不起作用,但有关更多详细信息,我建议阅读this excellent post on kinds。
在ghci中询问有关Applicative的信息时,请考虑返回的第一行:
λ> :info Applicative
class Functor f => Applicative (f :: * -> *) where
看到f :: * -> *
位吗?它告诉您期望的种类。对种类一无所知?我会给你最简单的方法。每次添加参数类型时,基本上就是在告诉您需要“另一种类型来构建类型”。
例如,当您说Maybe a
时,您说“要拥有一个Maybe,我需要一个”。或者,当您写Either a b
时,您说“我的两种类型都取决于类型a和类型b”。您可以通过在ghci中简称:kind
或:k
来获取有关种类的信息。考虑:
λ> :kind Bool
Bool :: *
λ> :kind Maybe
Maybe :: * -> *
λ> :kind Either
Either :: * -> * -> *
每个“ *”代表一种类型。 Bool
是一种简单的类型。 Maybe
所有人都期待另一种类型。 Either
所有人都期待另外一种类型。请注意输入时的区别:
λ> :kind Maybe Bool
Maybe Bool :: *
现在考虑:
λ> :kind Either Bool
Either Bool :: * -> *
看到这种* -> *
吗?当询问有关Applicative
的信息时,这正是我们所看到的。 (对Functor
和Monad
的期望也是相同的。)
这意味着typeclass仅对 latest 参数类型起作用。 Either
也是如此。如您所见:
λ> let fun = (++ " !")
λ> fun <$> Left "Oops"
Left "Oops"
这没有任何作用,因为Functor
的{{1}}都不是这两种类型的仿函数:它只是最后一种类型的{{1}的Either
}。使用简单的b
和Either a b
(或此处的中缀版本Functor
),我只能对fmap
的b进行操作,这就是为什么它可以工作的原因:< / p>
<$>
现在,回到您要尝试做的事情。您有一个Either a b
,所以λ> fun <$> Right "Oops"
Right "Oops !"
的种类为newtype Constant a b
。现在让我们来看一下Constant
的第二行,它将为我们提供* -> * -> *
的签名:
:info Applicative
现在始终考虑到这一点:pure
签名中的class Functor f => Applicative (f :: * -> *) where
pure :: a -> f a
不是 a
的{{1}}。更糟糕的是,在这个示例中,pure
中的a
是Constant a b
中的a
。因为如果您考虑善意,并且专门研究此签名,则会得到:
pure
但这不是您在做什么,不是吗?新类型中存储的是类型b
。并且您试图将Constant
类型放入其中。由于 pure :: b -> Constant a b
和a
可以不同,因此无法使用。
至于“纯正做什么”的“大问题”,这确实是一个问题,我将给您一个答案的开始。
b
是一种使初始值a
输入b
的方法。如签名所述:pure
。那没有帮助吗?好的,将您的a
视为“上下文”(我使用一个非常笼统的词,因为应用程序是一个非常笼统的概念)。 “上下文”可能是:我正在一个可能失败的世界(即Applicative f
)中工作。可能是:我在一个对一个问题有很多答案(即a -> f a
或Applicative
)的世界中工作。它可以有很多很多的东西-在您的示例中,这是一个上下文,在此上下文中,任何内容都不会被计算,并且常量将始终返回。问题在于,在您的示例中,不可能“猜测”什么是常数。
如我们所见,常量(您的上下文)不是值。它不是Maybe
中的List
。它是[]
的一部分。这就是实现使用a
和pure
的原因:您需要一种获取默认上下文的方法,并且f
始终具有可用的默认值。
最后,Monoid
是 hard 。因此,不立即了解它们是完全正常的。专注于阅读类型,尝试了解编译器告诉您的内容,它将变得更加容易。慢慢地重读本章,慢慢来。
答案 1 :(得分:0)
查看类型。 (永恒的建议!)
data Const a b = MkConst a
例如,
> MkConst 1 :: Const Int Bool
> MkConst 1 :: Const Int Float
> MkConst 1 :: Const Int [(Integer, Maybe [()])]
下一步
instance Functor (Const a) where
fmap f (MkConst x) = MkConst x
什么? .... 可是等等;实际上是
instance Functor (Const a) where
-- fmap :: (b -> c) -> (Const a b) -> (Const a c)
fmap f (MkConst x ) = (MkConst x )
现在更清楚了,不是吗? Const a
是仿函数。在容器范式下,在Functor f => f b
中,f
是外壳,而b
是其中的东西。 f ~ Const a
是 empty 外壳。怀着什么本来可以,但是没有。但这仅仅是文学。在日常的范式和隐喻下,事情不必说得通。唯一重要的是-类型-适合。
不要试图把它全都放在脑子里;把它放在纸上。这是非常重要的建议,我希望自己早一点。不要跳过任何事情;根据需要重复多次。
类似地,
instance Monoid a => Applicative (Const a) where
-- pure :: Applicative f => b -> f b
-- pure :: Applicative (Const a) => b -> (Const a b)
pure x = (MkConst x )
等等,什么?是x :: a
还是x :: b
?擦在其中。它是后者,但您要使其成为前者。
Const
是 empty 外壳,还记得吗?即使将x
放在其中,即使它记录了尝试,它仍然为空。换句话说,x
本身被忽略,只有其类型起作用:
-- pure :: (Monoid a, Applicative (Const a)) =>
-- b -> (Const a b)
pure x = (MkConst mempty )
(在这里,我为类型和数据构造函数使用了不同的名称,以消除模棱两可的地方,这一开始可能会造成混淆)。