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

Allen Han 3 haskell functional-programming applicative

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

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

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

Robin Zigmon.. 7

The other two answers have given the detail of how this is calculated - but I thought I might chime in with a more "intuitive" answer to explain how, without going through a detailed calculation, one can "see" that the result must be 508.

As you implied, every Applicative (in fact, even every Functor) can be viewed as a particular kind of "context" which holds values of a given type. As simple examples:

  • Maybe a is a context in which a value of type a might exist, but might not (usually the result of a computation which may fail for some reason)
  • [a] is a context which can hold zero or more values of type a, with no upper limit on the number - representing all possible outcomes of a particular computation
  • IO a is a context in which a value of type a is available as a result of interacting with "the outside world" in some way. (OK that one isn't so simple...)

And, relevant to this example:

  • r -> a is a context in which a value of type a is available, but its particular value is not yet known, because it depends on some (as yet unknown) value of type r.

The Applicative methods can be very well understood on the basis of values in such contexts. pure embeds an "ordinary value" in a "default context" in which it behaves as closely as possible in that context to a "context-free" one. I won't go through this for each of the 4 examples above (most of them are very obvious), but I will note that for functions, pure = const - that is, a "pure value" a is represented by the function which always produces a no matter what the source value.

Rather than dwell on how <*> can best be described using the "context" metaphor though, I want to dwell on the particular expression:

f <$> a <*> b

where f is a function between 2 "pure values" and a and b are "values in a context". This expression in fact has a synonym as a function: liftA2. Although using the liftA2 function is generally considered less idiomatic than the "applicative style" using <$> and <*>, the name emphasies that the idea is to "lift" a function on "ordinary values" to one on "values in a context". And when thought of like this, I think it is usually very intuitive what this does, given a particular "context" (ie. a particular Applicative instance).

So the expression:

(+) <$> a <*> b

for values a and b of type say f Int for an Applicative f, behaves as follows for different instances f:

  • if f = Maybe, then the result, if a and b are both Just values, is to add up the underlying values and wrap them in a Just. If either a or b is Nothing, then the whole expression is Nothing.
  • if f = [] (the list instance) then the above expression is a list containing all sums of the form a' + b' where a' is in a and b' is in b.
  • if f = IO, then the above expression is an IO action that performs all the I/O effects of a followed by those of b, and results in the sum of the Ints produced by those two actions.

So what, finally, does it do if f is the function instance? Since a and b are both functions describing how to get a given Int given an arbitrary (Int) input, it is natural that lifting the (+) function over them should be the function that, given an input, gets the result of both the a and b functions, and then adds the results.

And that is, of course, what it does - and the explicit route by which it does that has been very ably mapped out by the other answers. But the reason why it works out like that - indeed, the very reason we have the instance that f <*> g = \x -> f x (g x), which might otherwise seem rather arbitrary (although in actual fact it's one of the very few things, if not the only thing, that will type-check), is so that the instance matches the semantics of "values which depend on some as-yet-unknown other value, according to the given function". And in general, I would say it's often better to think "at a high level" like this than to be forced to go down to the low-level details of exactly how computations are performed. (Although I certainly don't want to downplay the importance of also being able to do the latter.)

[实际上,从哲学的角度来看,更确切地说,定义是正确的,只是因为类型检查是“自然的”定义,而实例恰好是碰巧的巧合,一个不错的“意思”。当然,数学充满了如此快乐的“巧合”,事实证明它们背后有很深的原因。]


Bergi.. 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如果你知道它是如何表现的单子.


Willem Van O.. 6

让我们先来看看如何fmap(<*>)一个函数的定义:

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


归档时间:

查看次数:

209 次

最近记录:

1 年,2 月 前