应用仿函数评估对我来说并不清楚

问题描述 投票:3回答:4

我正在阅读“了解你是一个很好的Haskell!”并且我对某个代码块的评估的解释感到磕磕绊绊。我已多次阅读这些解释,并开始怀疑即使是作者也能理解这段代码的作用。

ghci> (+) <$> (+3) <*> (*100) $ 5
508

应用程序函数在某些上下文中将函数应用于某个上下文中的值,以在某些上下文中获得某些结果。我花了几个小时来研究这个代码块,并对这个表达式的评估方式提出了一些解释,但没有一个是令人满意的。我知道(5 + 3)+(5 * 100)是508,但问题是这个表达式。有没有人对这段代码有明确的解释?

haskell functional-programming applicative
4个回答
6
投票

它正在使用应用实例来实现功能。你的代码

(+) <$> (+3) <*> (*100) $ 5

被评估为

( (\a->b->a+b) <$> (\c->c+3) <*> (\d->d*100) ) 5
( (\x -> (\a->b->a+b) ((\c->c+3) x)) <*> (\d->d*100) ) 5
( (\x -> (\a->b->a+b) (x+3)) <*> (\d->d*100) ) 5
( (\x -> b -> (x+3)+b) <*> (\d->d*100) ) 5
( (\x->b->(x+3)+b) <*> (\d->d*100) ) 5
(\y -> ((\x->b->(x+3)+b) y) ((\d->d*100) y)) 5
(\y -> (b->(y+3)+b) (y*100)) 5
(\y -> (y+3)+(y*100)) 5
(5+3)+(5*100)

其中<$>fmap或只是函数组合.<*>ap,如果你知道它在monad上的行为。


6
投票

另外两个答案给出了计算方法的细节 - 但我想我可能会用更“直观”的答案来解释如何在不经过详细计算的情况下“看到”结果必须为508 。

正如你暗示的那样,每个Applicative(实际上,甚至每个Functor)都可以被视为一种特定的“上下文”,它包含给定类型的值。举个简单的例子:

  • Maybe a是一个上下文,其中可能存在a类型的值,但可能不存在(通常是由于某种原因可能会失败的计算结果)
  • [a]是一个上下文,它可以保存零个或多个a类型的值,没有数字上限 - 表示特定计算的所有可能结果
  • IO a是一种上下文,其中a类型的值可以通过某种方式与“外部世界”进行交互。 (好吧,那不是那么简单......)

并且,与此示例相关:

  • r -> a是一个上下文,其中a类型的值可用,但其特定值尚不清楚,因为它取决于r类型的某些(尚未知)值。

基于这种背景下的价值观,可以很好地理解Applicative方法。 pure在“默认上下文”中嵌入一个“普通值”,在该上下文中,它与“无上下文”的行为尽可能接近。我不会对上面的4个例子中的每个例子(大多数都非常明显)进行讨论,但是我会注意到,对于函数,pure = const - 也就是说,“纯值”a由始终产生的函数表示a无论什么来源价值。

我不想详述使用“上下文”比喻如何最好地描述<*>,而是想详述一下:

f <$> a <*> b

其中f是2“纯值”之间的函数,ab是“上下文中的值”。事实上,这个表达式有一个同义词作为函数:liftA2。虽然使用liftA2函数通常被认为不如使用<$><*>的“适用风格”惯用,但该名称强调该想法是将“普通值”上的函数“提升”为“上下文中的值”。当我想到这样的时候,我认为这通常非常直观,给定一个特定的“背景”(即特定的Applicative实例)。

所以表达式:

(+) <$> a <*> b

对于值ab类型,如f Int为适用的f,对于不同的实例f表现如下:

  • 如果f = Maybe,那么结果,如果ab都是Just值,是将基础值加起来并将它们包装在Just中。如果abNothing,那么整个表达式就是Nothing
  • 如果f = [](列表实例)那么上面的表达式是一个包含a' + b'形式的所有总和的列表,其中a'ab'b
  • 如果f = IO,那么上面的表达式是一个IO动作,执行a的所有I / O效果,然后是b的那些,并导致这两个动作产生的Ints的总和。

那么,最后,如果f是函数实例呢?由于ab都是描述如何在给定任意(Int)输入的情况下获得给定Int的函数,因此将(+)函数提升到它们上应该是这样的函数,给定输入,得到ab的结果。 f <*> g = \x -> f x (g x)函数,然后添加结果。

当然,这就是它的作用 - 以及它所做的明确路线,其他答案已经非常巧妙地绘制出来。但是之所以这样做的原因 - 实际上,我们之所以有fmap这个实例,其原因可能看似相当随意(尽管实际上它是极少数事情之一,如果不是唯一的事情,将会输入-check),使得实例匹配“依赖于某些尚未知的其他值的值,根据给定的函数”的语义。总的来说,我认为通常更好地考虑这样的“高水平”,而不是被迫深入到准确计算如何执行的低级细节。 (虽然我当然不想淡化能够做后者的重要性。)

[实际上,从哲学的角度来看,可能更准确地说定义是因为它只是因为它是类型检查的“自然”定义,而且实例然后采用这种定义只是快乐的巧合。一个很好的“意义”。数学当然充满了如此快乐的“巧合”,结果却背后有很深的理由。


5
投票

我们先来看看如何为函数定义(<*>)instance Functor ((->) r) where fmap = (.) instance Applicative ((->) a) where pure = const (<*>) f g x = f x (g x) liftA2 q f g x = q (f x) (g x)

 (+) <$> (+3) <*> (*100)  $ 5

我们要评估的表达方式是:

((+) <$> (+3)) <*> (*100) $ 5

或者更详细:

(<$>)

如果我们因此评估fmap,它是(+) . (+3) 的中缀同义词,我们因此看到它等于:

((+) . (+3)) <*> (*100) $ 5

所以这意味着我们的表达相当于:

f

接下来我们可以应用顺序应用程序。因此,(+) . (+3)等于g,而(*100)则是\x -> ((+) . (+3)) x ((*100) x) 。这意味着我们构造一个看起来像这样的函数:

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

我们现在可以简化这个并将其重写为:

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

然后将其重写为:

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

因此,我们构建了一个看起来像这样的函数:

\x -> 101 * x + 3

或者更简单:

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

如果我们然后计算:

101 * 5 + 3

然后我们当然获得:

505 + 3

因此:

508

这是预期的:

        a <$> b <*> c  =  liftA2 a b c

3
投票

对于任何应用程序,

    liftA2 a  b     c      x 
=          a (b x) (c x)          -- by definition;
=       (a . b) x  (c x)
=     ((a <$> b) <*> c)    x

对于功能,

        (+) <$> (+3) <*> (*100)  $  5
=
  liftA2 (+)   (+3)      (*100)     5
=
         (+)  ((+3) 5)  ((*100) 5)
=
              (5+3)  +  (5*100)

从而

(<*>)

(这个答案的长版本如下。)

纯数学没有时间。 Pure Haskell没有时间。用动词(“applicative functor apply”等)说话可能会令人困惑(“适用......何时?......”)。

相反, ( $ ) :: (a -> b) -> a -> b (<$>) :: (a -> b) -> f a -> f b (<*>) :: f (a -> b) -> f a -> f b (=<<) :: (a -> f b) -> f a -> f b 是一个组合器,它结合了一个“计算”(由一个应用函子表示),它带有一个函数(在某些上下文中)和一个相同类型的“计算”,携带一个值(在类似的上下文中),组合成一个“计算“执行该函数对该值的应用(在这种情况下)。

“计算”用于将其与纯Haskell“计算”进行对比。 “计算”可能是也可能不是纯粹的,这是一个正交的问题。但主要是它的意思是“计算”体现了一种通用的函数调用协议。除了/执行函数应用程序的一部分之外,它们可能会“做”某些事情。或者在类型中,

a <$> b <*> c

对于函数,上下文是应用程序(另一个),并且为了恢复值 - 无论是函数还是参数 - 执行对公共参数的应用程序。

(忍受我,我们几乎在那里)。 liftA2 a b c模式也可以表达为 liftA2 h x y s = let x' = x s -- embellished application of h to x and y y' = y s in -- in context of functions, or Reader h x' y' -- liftA2 h x y = let x' = x -- non-embellished application, or Identity -- y' = y in -- h x' y' -- liftA2 h x y s = let (x',s') = x s -- embellished application of h to x and y -- (y',s'') = y s' in -- in context of -- (h x' y', s'') -- state-passing computations, or State -- liftA2 h x y = let (x',w) = x -- embellished application of h to x and y -- (y',w') = y in -- in context of -- (h x' y', w++w') -- logging computations, or Writer -- liftA2 h x y = [h x' y' | -- embellished application of h to x and y -- x' <- x, -- in context of -- y' <- y ] -- nondeterministic computations, or List -- ( and for Monads we define `liftBind h x k =` and replace `y` with `k x'` -- in the bodies of the above combinators; then liftA2 becomes liftBind: ) -- liftA2 :: (a -> b -> c) -> f a -> f b -> f c -- liftBind :: (a -> b -> c) -> f a -> (a -> f b) -> f c -- (>>=) = liftBind (\a b -> b) :: f a -> (a -> f b) -> f b 。因此,“函数”应用函子“计算”类型由定义

> :t let liftA2 h x y r = h (x r) (y r) in liftA2
       :: (a -> b -> c)  -> (t -> a)  -> (t -> b)  -> (t -> c)

> :t liftA2   -- the built-in one
liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c

确实,

f a  ~  (t -> a)  ~  (->) t a

即当我们采取f ~ (->) t,即 (+) <$> (+3) <*> (*100) $ 5 = liftA2 (+) (+3) (*100) 5 = (+) ((+3) 5) ((*100) 5) = (+) (5+3) (5*100) = (5+3) + (5*100) 时类型匹配。

所以,我们已经在那里:

liftA2

这就是为这种类型定义Applicative ((->) t) => ...的原因,instance Applicative ((->) t) where pure x t = x liftA2 h x y t = h (x t) (y t)

(<*>)

没有必要定义The source code sayspure, ((<*>) | liftA2)

最小的完整定义

a <$> b <*> c

所以现在你一直想要问很久,为什么liftA2 a b c相当于(<*>)

简短的回答是,它就是。一个可以用另一个来定义 - 即liftA2可以通过 g <*> x = liftA2 id g x -- i.e. (<*>) = liftA2 id = liftA2 ($) -- (g <*> x) t = liftA2 id g x t -- = id (g t) (x t) -- = (id . g) t (x t) -- = (id <$> g <*> x) t -- = g t (x t) 来定义,

in the source

(正如它定义的那样h <$> g = pure h <*> g),

每个申请编织者必须遵循的法律是liftA2 h g x == pure h <*> g <*> x -- h g x == (h g) x

最后,

<*>

因为infixl 4 <*>与左边相关:它是qazxswpoi。

© www.soinside.com 2019 - 2024. All rights reserved.