简体   繁体   English

在Haskell中理解作为应用的函数

[英]Understanding Functions as Applicatives in Haskell

I've recently been trying to learn Haskell with the "Learn You a Haskell" and have been really struggling with understanding functions as Applicatives. 我最近一直在尝试通过“向您学习Haskell”来学习Haskell,并且一直在努力理解作为Applicatives的功能。 I should point out that using other types of Applicatives like Lists and Maybe I seem to understand well enough to use them effectively. 我应该指出,使用其他类型的Applicatives(例如List)和Maybe我似乎已经足够了解,可以有效地使用它们。

As I tend to do when trying to understand something is I tried to play with as many examples as I could and once the pattern emerges things tend to make sense. 正如我在尝试理解某些事物时通常会做的那样,我会尝试使用尽可能多的示例,并且一旦模式出现,事情就会变得有意义。 As such I tried a few examples. 因此,我尝试了一些示例。 Attached are my notes of several examples I tried along with a diagram I drew to try to visualize what was happening. 随附的注释是我尝试的几个示例以及绘制的图表以可视化正在发生的情况的图表。

在此处输入图片说明

The definition of funct doesnt seem to relevant to the outcome but in my tests I used a function with the following definition: funct的定义似乎与结果无关,但是在我的测试中,我使用了具有以下定义的函数:

funct :: (Num a) => a -> a -> a -> a

At the bottom I tried to show the same thing as in the diagrams just using normal math notation. 在底部,我尝试使用普通的数学符号显示与图中相同的内容。

So all of this is well and good, I can understand the pattern when I have some function of an arbitrary number of arguments (though needs 2 or more) and apply it to a function that takes one argument. 因此,所有这些都很好,当我具有任意数量的参数的函数(尽管需要2个或更多)时,我可以理解模式,并将其应用于采用一个参数的函数。 However intuitively this pattern doesn't make that much sense to me. 但是从直觉上来说,这种模式对我来说没有太大意义。

So here are the specific questions I have: 所以这是我有的具体问题:

What is the intuitive way to understand the pattern I'm seeing, particularly if i view an Applicative as a container (which is how I view Maybe and lists)? 理解我所看到的模式的直观方式是什么,特别是如果我将Applicative视为容器(这就是我查看Maybe和列表的方式)的时候?

What is the pattern when the function on the right of the <*> takes more than a single argument (I've mostly been using the function (+3) or (+5) on the right)? <*>右边的函数接受多个参数时(我主要使用右边的(+3)(+5)函数(+5) ,该模式是什么?

why is the function on the right hand side of the <*> applied to the second argument of the function on the left side. 为什么<*>右侧的函数应用于左侧函数的第二个自变量。 For example if the function on the right hand side were f() then funct(a,b,c) turns into funct (x, f(x), c) ? 例如,如果右边的函数是f()那么funct(a,b,c)变成funct (x, f(x), c)

Why does it work for funct <*> (+3) but not for funct <*> (+) ? 为什么它对funct <*> (+3)无效,但对funct <*> (+)无效? Moreover it DOES work for (\\ ab -> 3) <*> (+) 而且它确实适用于(\\ ab -> 3) <*> (+)

Any explanation that gives me a better intuitive understanding for this concept would be greatly appreciated. 任何使我对这个概念有更好的直观理解的解释将不胜感激。 I read other explanations such as in the book I mentioned that explains functions in terms of ((->)r) or similar patterns but even though I know how to use the -> ) operator when defining a function I'm not sure i understand it in this context. 我读过其他说明,例如在我提到的书中,它以((->)r)或类似模式解释了函数,但是即使我知道在定义函数时如何使用->运算符,我也不确定在这种情况下理解它。

Extra Details: 额外详情:

I want to also include the actual code I used to help me form the diagrams above. 我还想包括用来帮助我形成以上图表的实际代码。

First I defined funct as I showed above with: 首先,如上所示,我定义了funct:

funct :: (Num a) => a -> a -> a -> a

Throughout the process i refined funct in various ways to understand what was going on. 在整个过程中,我以各种方式完善了功能,以了解正在发生的事情。

Next I tried this code: 接下来,我尝试了以下代码:

funct a b c = 6 
functMod =  funct <*> (+3)
functMod 2 3

Unsuprisingly the result was 6 毫不奇怪,结果是6

So now I tried just returning each argument directly like this: 所以现在我尝试像这样直接返回每个参数:

funct a b c = a
functMod =  funct <*> (+3)
functMod 2 3 -- returns 2

funct a b c = b
functMod =  funct <*> (+3)
functMod 2 3 -- returns 5

funct a b c = c
functMod =  funct <*> (+3)
functMod 2 3 -- returns 3

From this I was able to confirm the second diagram is what was taking place. 由此,我可以确认第二张图正在发生。 I repeated this patterns to observe the third diagram as well (which is the same patterns extended on top a second time). 我也重复了这种模式以观察第三张图(这是第二次在顶部扩展的相同模式)。

You can usually understand what a function is doing in Haskell if you substitute its definition into some examples. 如果将其定义替换为一些示例,通常可以了解函数在Haskell中的作用。 You already have some examples and the definition you need is <*> for (->) a which is this: 您已经有一些示例,所需的定义是(->) a <*> ,这是这样的:

(f <*> g) x = f x (g x)

I don't know if you'll find any better intuition than just using the definition a few times. 我不知道您是否会发现比仅使用几次定义更好的直觉。

On your first example we get this: 在您的第一个示例中,我们得到以下信息:

  (funct <*> (+3)) x
= funct x ((+3) x)
= funct x (x+3)

(Since there was nothing I could do with funct <*> (+3) without a further parameter I just applied it to x - do this any time you need to.) (由于没有其他参数,我无法使用funct <*> (+3)进行任何操作,因此我将其应用于x在需要时随时执行此操作。)

And the rest: 其余的:

  (funct <*> (+3) <*> (+5)) x
= (funct x (x+3) <*> (+5)) x
= funct x (x+3) x ((+5) x)
= funct x (x+3) x (x+5)

  (funct <*> (+)) x
= funct x ((+) x)
= funct x (x+)

Notice you can't use the same funct with both of these - in the first it can take four numbers, but in the second it needs to take a number and a function. 请注意,你不能使用相同的funct与这两个-第一它可以采取四个数字,但在第二,它需要采取一些和功能。

  ((\a b -> 3) <*> (+)) x
= (\a b -> 3) x (x+)
= (\b -> 3) (x+)
= 3

  (((\a b -> a + b) <*> (+)) x
= (\a b -> a + b) x (x+)
= x + (x+)
= type error

You can view the function monad as a container. 可以将函数monad作为容器查看。 Note that it's really a separate monad for every argument-type, so we can pick a simple example: Bool . 请注意,对于每个参数类型,它实际上都是一个单独的monad,因此我们可以选择一个简单的示例: Bool

type M a = Bool -> a

This is equivalent to 这相当于

data M' a = M' { resultForFalse :: a
               , resultForTrue :: a  }

and the instances could be defined 实例可以定义

instance Functor M where            instance Functor M' where
  fmap f (M g) = M g'                 fmap f (M' gFalse gTrue) = M g'False g'True
   where g' False = f $ g False        where g'False = f $ gFalse
         g' True  = f $ g True               g'True  = f $ gTrue

and similar for Applicative and Monad . ApplicativeMonad相似。

Of course this exhaustive case-listing definition would become totally impractical for argument-types with more than a few possible values, but it's always the same principle. 当然,对于具有多个可能值的参数类型,这种详尽的案例列表定义将变得完全不切实际,但这始终是相同的原理。

But the important thing to take away is that the instances are always specific for one particular argument . 但重要的是,实例始终是特定于某个特定参数的 So, Bool -> Int and Bool -> String belong to the same monad, but Int -> Int and Char -> Int do not. 因此, Bool -> IntBool -> String属于同一个monad,但是Int -> IntChar -> Int不属于同一个单子。 Int -> Double -> Int does belong to the same monad as Int -> Int , but only if you consider Double -> Int as an opaque result type which has nothing to do with the Int-> monad. Int -> Double -> Int不属于同一个单子的Int -> Int ,但是只有当你考虑Double -> Int为具有无关的不透明结果类型Int->单子。

So, if you're considering something like a -> a -> a -> a then this is not really a question about applicatives/monads but about Haskell in general. 因此,如果您正在考虑类似a -> a -> a -> a a-> a- a -> a -> a -> a那么这实际上不是关于应用程序/单子的问题,而是关于Haskell的一般问题。 And therefore, you shouldn't expect that the monad=container picture gets you anywhere. 因此,您不应该期望monad = container图片可以带您到任何地方。 To understand a -> a -> a -> a as a member of a monad, you need to pick out which of the arrows you're talking about; 要了解a -> a -> a -> a a- a -> a -> a -> a a- a -> a -> a -> a作为monad的成员,您需要挑选出您正在谈论的箭头; in this case it's only the leftmost one, ie you have the value M (a->a->a) in the type M=(a->) monad. 在这种情况下,它只是最左边的一个,即您在type M=(a->) monad中具有值M (a->a->a) The arrows between a->a->a do not participate in the monadic action in any way; a->a->a之间的箭头不以任何方式参与单子动作。 if they do in your code, then it means you're actually mixing multiple monads together. 如果它们在您的代码中包含,则意味着您实际上是在混合多个monad。 Before you do that, you should understand how a single monad works, so stick to examples with only a single function arrow. 在执行此操作之前,您应该了解单个monad的工作原理,因此请坚持仅使用单个功能箭头的示例。

As pointed out by David Fletcher , (<*>) for functions is: 正如David Fletcher指出的 ,函数的(<*>)是:

(g <*> f) x = g x (f x)

There are two intuitive pictures of (<*>) for functions which, though not quite able to stop it from being dizzying, might help with keeping your balance as you go through code that uses it. 有两张直观的(<*>)函数图片,尽管不能完全阻止它令人目眩,但当您浏览使用它的代码时,它们可能有助于保持平衡。 In the next few paragraphs, I will use (+) <*> negate as a running example, so you might want to try it out a few times in GHCi before continuing. 在接下来的几段中,我将使用(+) <*> negate作为运行示例,因此您可能需要在GHCi中尝试几次,然后再继续。

The first picture is (<*>) as applying the result of a function to the result of another function: 第一张图片是(<*>)是将一个函数的结果应用于另一个函数的结果:

g <*> f = \x -> (g x) (f x)

For instance, (+) <*> negate passes an argument to both (+) and negate , giving out a function and a number respectively, and then applies one to the other... 例如, (+) <*> negate将参数传递给(+)negate ,分别给出一个函数和一个数字,然后将一个应用于另一个...

(+) <*> negate = \x -> (x +) (negate x)

... which explains why its result is always 0 . ...解释了为什么其结果始终为0

The second picture is (<*>) as a variation on function composition in which the argument is also used to determine what the second function to be composed will be 第二张图片是(<*>)作为函数组成的变体,其中该参数还用于确定将要组成的第二个函数

g <*> f = \x -> (g x . f) x

From that point of view, (+) <*> negate negates the argument and then adds the argument to the result: 从这个角度来看, (+) <*> negate否定参数,然后将参数添加到结果中:

(+) <*> negate = \x -> ((x +) . negate) x

If you have a funct :: Num a => a -> a -> a -> a , funct <*> (+3) works because: 如果您有一个funct :: Num a => a -> a -> a -> a funct <*> (+3) funct :: Num a => a -> a -> a -> afunct <*> (+3)之所以起作用是因为:

  • In terms of the first picture: (+ 3) x is a number, and so you can apply funct x to it, ending up with funct x ((+ 3) x) , a function that takes two arguments. 就第一张图片而言: (+ 3) x是一个数字,因此您可以对其应用funct x ,最后得到funct x ((+ 3) x) ,该函数需要两个参数。

  • In terms of the second picture: funct x is a function (of type Num a => a -> a -> a ) that takes a number, and so you can compose it with (+ 3) :: Num a => a -> a . 就第二张图片而言: funct x是一个带有数字的函数(类型为Num a => a -> a -> a ),因此您可以使用(+ 3) :: Num a => a -> a

On the other hand, with funct <*> (+) , we have: 另一方面,使用funct <*> (+) ,我们有:

  • In terms of the first picture: (+) x is not a number, but a Num a => a -> a function, and so you can't apply funct x to it. 就第一张图片而言: (+) x不是数字,而是Num a => a -> a函数,因此您无法对其应用funct x

  • In terms of the second picture: the result type of (+) , when seen as a function of one argument ( (+) :: Num a => a -> (a -> a) ), is Num a => a -> a (and not Num a => a ), and so you can't compose it with funct x (which expects a Num a => a ). 就第二张图片而言: (+)的结果类型,被视为一个参数( (+) :: Num a => a -> (a -> a) Num a => a -> a (+) :: Num a => a -> (a -> a)Num a => a -> a ,为Num a => a -> a (而不是Num a => a ),因此您不能将它与funct x (期望使用Num a => a )。

For an arbitrary example of something that does work with (+) as the second argument to (<*>) , consider the function iterate : 对于可以将(+)作为(<*>)的第二个参数的东西的任意示例,请考虑以下函数iterate

iterate :: (a -> a) -> a -> [a]

Given a function and an initial value, iterate generates an infinite list by repeatedly applying the function. 给定一个函数和一个初始值, iterate通过重复应用该函数来生成无限列表。 If we flip the arguments to iterate , we end up with: 如果我们将参数翻转为iterate ,我们将得到:

flip iterate :: a -> (a -> a) -> [a]

Given the problem with funct <*> (+) was that funct x wouldn't take a Num a => a -> a function, this seems to have a suitable type. 鉴于funct <*> (+)的问题在于funct x不会使用Num a => a -> a函数,这似乎具有合适的类型。 And sure enough: 确实:

GHCi> take 10 $ (flip iterate <*> (+)) 1
[1,2,3,4,5,6,7,8,9,10]

(On a tangential note, you can leave out the flip if you use (=<<) instead of (<*>) . That, however, is a different story .) (在切向音符上,如果使用(=<<)而不是(<*>) ,则可以省略flip但是,这是另一回事了 。)


As a final aside, neither of the two intuitive pictures lends itself particularly well to the common use case of applicative style expressions such as: 最后,两个直观的图片都不适合应用样式表达的常见用例,例如:

(+) <$> (^2) <*> (^3)

To use the intuitive pictures there, you'd have to account for how (<$>) for functions is (.) , which murks things quite a bit. 要在其中使用直观的图片,您必须考虑函数(.) (<$>)如何,这会使事情变得相当混乱。 It is easier to just see the entire thing as lifted application instead: in this example, we are adding up the results of (^2) and (^3). 将整个事情看作是提升后的应用程序会更容易:在此示例中,我们将(^ 2)和(^ 3)的结果相加。 The equivalent spelling as... 等同于...

liftA2 (+) (^2) (^3)

... somewhat emphasises that. ...在某种程度上强调了这一点。 Personally, though, I feel one possible disadvantage of writing liftA2 in this setting is that, if you apply the resulting function right in the same expression, you end up with something like... 但是,就我个人而言,我觉得在此设置中编写liftA2一个可能的缺点是,如果在同一表达式中正确应用结果函数,则最终会出现类似...

liftA2 (+) (^2) (^3) 5

... and seeing liftA2 followed by three arguments tends to make my brain tilt. ...并且看到跟着三个参数的liftA2会使我的大脑倾斜。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM