作为applicative functors(Haskell/LYAH)

pla*_*ian 36 haskell applicative

学习Haskell的第11章介绍了以下定义:

instance Applicative ((->) r) where
    pure x = (\_ -> x)
    f <*> g = \x -> f x (g x)
Run Code Online (Sandbox Code Playgroud)

在这里,作者参与了一些不寻常的挥手("<*>的实例实现有点神秘,所以如果我们只是[在没有解释它的情况下显示它的话]它是最好的").我希望这里的某个人可以帮助我搞清楚.

根据应用类定义, (<*>) :: f (a -> b) -> f a -> f b

在实例中,替换((->)r)f:r->(a->b)->(r->a)->(r->b)

所以第一个问题是,我该如何从这种类型中获得f <*> g = \x -> f x (g x)

但即使我认为最后一个公式是理所当然的,我也很难同意我给GHCi的例子.例如:

Prelude Control.Applicative> (pure (+5)) <*> (*3) $ 4
17
Run Code Online (Sandbox Code Playgroud)

这个表达式看起来与之相符f <*> g = \x -> f (g x)(请注意,此版本x之后不会出现f.

我意识到这很混乱,所以感谢与我的关系.

Tik*_*vis 35

首先,请记住如何fmap为应用程序定义:

fmap f x = pure f <*> x
Run Code Online (Sandbox Code Playgroud)

这意味着您的示例与之相同(fmap (+ 5) (* 3)) 4.在fmap对功能的功能仅仅是组成,所以你确切的表达是一样的((+ 5) . (* 3)) 4.

现在,让我们考虑为什么实例的编写方式.什么<*>基本上将函数中的函数应用于仿函数中的值.专门用于(->) r,这意味着它将函数返回的函数应用于函数返回r的值r.返回函数的函数只是两个参数的函数.所以,真正的问题是:你会如何运用的两个参数(函数ra,返回b)的值a由函数返回r

首先要注意的是你必须返回一个类型的值,(->) r这意味着结果也必须是一个函数r.供参考,这是<*>功能:

f <*> g = \x -> f x (g x)
Run Code Online (Sandbox Code Playgroud)

因为我们想要返回一个取值类型的函数r,x :: r.我们返回的函数必须有一个类型r -> b.我们如何获得类型的值b?好吧,我们有一个功能f :: r -> a -> b.由于r它将成为结果函数的参数,我们可以免费获得.所以现在我们有了一个函数a -> b.所以,只要我们有一些类型的值a,我们就可以得到一个类型的值b.但是我们如何获得类型的价值a?好吧,我们有另一个功能g :: r -> a.因此我们可以获取type r(参数x)的值,并使用它来获取type的值a.

所以最后的想法很简单:我们使用参数首先a通过插入来获取类型的值g.参数有类型r,g有类型r -> a,所以我们有一个a.然后,我们将参数和新值插入f.我们需要两个因为f有类型r -> a -> b.一旦我们插入一个r和一个a,我们有一个b1.由于参数是lambda,结果有一个类型r -> b,这就是我们想要的.

  • 有趣的事实:`(( - >)r)`的`<*>`的定义可以免费派生.尝试将类型(`(r - > a - > b) - >(r - > a) - >(r - > b)`)插入Djinn! (2认同)
  • 我发现上面的解释非常有用,但有点难以理解.如果有人想要更多的工作,这里是一个解释我如何将上述应用到一个真实的例子,以便我可以更好地理解它:https://wjdhamilton.blogspot.co.uk/2016/08/ffxgx.html (2认同)

小智 22

通过你原来的问题,我认为你可能错过了一个微妙但非常关键的观点.使用LYAH的原始示例:

(+) <$> (+3) <*> (*100) $ 5
Run Code Online (Sandbox Code Playgroud)

这与:

pure (+) <*> (+3) <*> (*100) $ 5
Run Code Online (Sandbox Code Playgroud)

这里的关键是purebefore (+),它具有拳击(+)作为应用的效果.如果你看看如何pure定义,你可以看到要取消它,你需要提供一个额外的参数,可以是任何东西.应用<*>(+) <$> (+3),我们得到

\x -> (pure (+)) x ((+3) x)
Run Code Online (Sandbox Code Playgroud)

注意到(pure (+)) x,我们正在申请xpure拆箱(+).所以我们现在有

\x -> (+) ((+3) x)
Run Code Online (Sandbox Code Playgroud)

添加(*100)到获取(+) <$> (+3) <*> (*100)<*>再次申请,我们得到

\y -> (\x -> (+) ((+3) x)) y ((*100) y) {Since f <*> g = f x (g x)}

5  -> (\x -> (+) ((+3) x)) 5 ((*100) 5)

(\x -> (+) ((+3) x)) 5 (500)

5 -> (+) ((+3) 5) (500)

(+) 8 500

508
Run Code Online (Sandbox Code Playgroud)

所以总的来说,xafter f不是我们的二元运算符的第一个参数,它用于UNBOX里面的运算符pure.

  • 这绝对是一个关键点!它解释了 x 的作用以及为什么第一个函数接受的参数数量必须等于应用函数的数量。 (3认同)
  • @JamesHamilton 这是一针见血的答案。它应该是*的*答案。 (2认同)

lef*_*out 15

"在实例中,取代((->)r)f:r->(a->b)->(r->a)->(r->b)"

为什么,这不对.实际上(r->(a->b)) -> (r->a) -> (r->b),这就是(r->a->b) -> (r->a) -> r -> b.也就是说,我们映射一个中缀和一个函数,它将中缀的右手参数返回给一个只接受中缀'LHS并返回其结果的函数.例如,

Prelude Control.Applicative> (:) <*> (\x -> [x]) $ 2
[2,2]
Run Code Online (Sandbox Code Playgroud)