如何在“应用”上定义“纯”功能?

时间:2018-10-26 07:19:30

标签: haskell applicative

《第一原理的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的作用吗,为什么他们只定义puremempty上使用一些Monoid?以及为什么这样:

pure x = Constant { getConstant = x }

不编译吗?我认为pure只是将类型a的值转换为类型f a的值,在这个示例中f只是Constant。为什么与source code不同?

2 个答案:

答案 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的信息时,这正是我们所看到的。 (对FunctorMonad的期望也是相同的。)

这意味着typeclass仅对 latest 参数类型起作用。 Either也是如此。如您所见:

λ> let fun = (++ " !")
λ> fun <$> Left "Oops"
Left "Oops"

这没有任何作用,因为Functor的{​​{1}}都不是这两种类型的仿函数:它只是最后一种类型的{{1}的Either }。使用简单的bEither 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中的aConstant a b中的a。因为如果您考虑善意,并且专门研究此签名,则会得到:

pure

但这不是您在做什么,不是吗?新类型中存储的是类型b。并且您试图将Constant类型放入其中。由于 pure :: b -> Constant a b a可以不同,因此无法使用。

至于“纯正做什么”的“大问题”,这确实是一个问题,我将给您一个答案的开始。

b是一种使初始值a输入b的方法。如签名所述:pure。那没有帮助吗?好的,将您的a视为“上下文”(我使用一个非常笼统的词,因为应用程序是一个非常笼统的概念)。 “上下文”可能是:我正在一个可能失败的世界(即Applicative f)中工作。可能是:我在一个对一个问题有很多答案(即a -> f aApplicative)的世界中工作。它可以有很多很多的东西-在您的示例中,这是一个上下文,在此上下文中,任何内容都不会被计算,并且常量将始终返回。问题在于,在您的示例中,不可能“猜测”什么是常数。

如我们所见,常量(您的上下文)不是。它不是Maybe中的List。它是[]的一部分。这就是实现使用apure的原因:您需要一种获取默认上下文的方法,并且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  )

(在这里,我为类型和数据构造函数使用了不同的名称,以消除模棱两可的地方,这一开始可能会造成混淆)。