简体   繁体   English

理解Haskell中的绑定函数

[英]Understanding bind function in Haskell

I am familiar with monads in category theory (they are a very easy concept, in fact), yet >>= function in Haskell completely puzzles me. 我熟悉类别理论中的monad(实际上它们是一个非常简单的概念),然而>>= Haskell中的函数完全让我困惑。 Ok, so applying bind to a value of M a and a function a -> M u is the same as first applying the monad to this function, then evaluating it at the specified value and multiplying the result: a >>= f is the same as join $ (fmap f) $ a . 好吧,所以将绑定应用于M a的值和函数a -> M u与首先将monad应用于此函数,然后在指定值处对其进行评估并将结果相乘: a >>= f是与join $ (fmap f) $ a But how is this a natural description of computation? 但这是如何自然地描述计算? Is there some useful way of looking at it that would help me understand it? 是否有一些有用的方法可以帮助我理解它?

Is there some nice article somewhere that is not geared towards someone fresh from the C++ jungle? 在某个地方是否有一些适合C ++丛林新鲜事物的文章?

Consider the monadic function composition operator <=< . 考虑monadic函数组合运算符<=< This is analogous to . 这类似于. except it works on monadic functions. 除了它适用于monadic函数。 It can be defined simply in terms of >>= , so learning about one will educate us about the other. 它可以简单地用>>=来定义,所以学习一个将教育我们另一个。

(<=<) :: (a -> m b) -> (b -> m c) -> a -> m c
(f <=< g) x =  g x >>= f

(.) :: (a -> b) -> (b -> c) -> a -> c
(f . g) x = g x |> f
  where z |> h = h z

In the case of . 在这种情况下. , g is "performed" first, and then f is performed on the output of g . g首先“执行”,然后对g的输出执行f In the case of <=< , g and its effects are "performed" first, and then f and its effects are performed. <=<g的情况下,首先“执行” 其效果 ,然后执行f及其效果。 It's a bit of a misnomer to say that one happens "before" the other, actually, since not all monads work that way. 说一个人在“另一个”之前发生,实际上,因为不是所有的monad都是那样工作的,这有点用词不当。

Perhaps it is more accurate to say that f can take advantage of additional contextual information provided by g . 或许更准确地说f可以利用g提供的其他上下文信息。 But that's not entirely correct, since g could potentially take away contextual information. 但是,这并不完全正确,因为g可能带走的上下文信息。 If you want to 100% correctly describe monads, you really have to walk on eggshells. 如果你想100%正确地描述monad,你真的必须走在蛋壳上。

But in almost all nontrivial cases, f <=< g means that the effects (as well as the result ) of the monadic function g will subsequently influence the behavior of the monadic function f . 但是在几乎所有非平凡的情况下, f <=< g意味着单子函数g效果 (以及结果 )随后会影响monadic函数f的行为。


To address questions about v >>= f = join (fmap fv) 解决有关v >>= f = join (fmap fv)

Consider f :: a -> mb and v :: ma . 考虑f :: a -> mbv :: ma What does it mean to fmap fv ? fmap fv是什么意思? Well fmap :: (c -> d) -> mc -> md , and in this case c = a and d = mb , so fmap f :: ma -> m (mb) . 那么fmap :: (c -> d) -> mc -> md ,在这种情况下c = ad = mb ,所以fmap f :: ma -> m (mb) Now, of course, we can apply v :: ma to this function, resulting in m (mb) . 当然,现在我们可以将v :: ma应用于此函数,从而得到m (mb) but what exactly does that result type m (mb) mean? 但是m (mb)结果类型究竟是什么意思呢?

The inner m represents the context from produced from f . 内部 m表示从f产生的上下文。 The outer m represents the context originating from v (nb fmap should not disturb this original context). 外部 m表示源自v的上下文(nb fmap不应该干扰这个原始上下文)。

And then you join that m (mb) , smashing those two contexts together into ma . 然后你join m (mb) ,把这两个背景粉碎成ma This is the heart of the definition of Monad : you must provide a way to smash contexts together. 这是Monad定义的核心:你必须提供一种粉碎上下文的方法。 You can inspect the implementation details of various Monad instances to try and understand how they "smash" contexts together. 您可以检查各种Monad实例的实现细节,以尝试了解它们如何“粉碎”上下文。 The takeaway here, though, is that the "inner context" is unobservable until you merge it with the "outer context". 然而,这里的内容是,“内部上下文”在您将其与“外部上下文”合并之前是不可观察的。 If you use v >>= f , then there is no actual notion of the function f receiving a pure value a and producing a simple monadic result mb . 如果你使用v >>= f ,那么函数f接收纯值a并产生简单monadic结果mb 实际概念。 Instead, we understand that f acts inside the original context of v . 相反,我们理解f v 的原始上下文中起作用。

Hmm. 嗯。 I think a good way to think of it is that >>= lets you compose computations; 我认为考虑它的好方法是>>=允许你组合计算; the computations themselves are in the form a -> mb . 计算本身的形式为a -> mb So mb just represents the result of a computation. 所以mb只代表计算的结果

So a computation just takes some value and produces some result. 因此计算只需要一些值并产生一些结果。 A good example here is the list type: a -> [b] represents a non-deterministic computation. 这里一个很好的例子是列表类型: a -> [b]表示非确定性计算。 It takes one input but can produce multiple results. 它需要一个输入,但可以产生多个结果。 By itself, the a -> [b] as a computation makes sense. 就其本身而言, a -> [b]作为计算是有意义的。 But how would you combine these? 但是你会如何结合这些呢? The natural answer is that you would perform each consecutive "computation" on all of the previous results. 自然的答案是,您将对所有先前的结果执行每个连续的“计算”。 And this is exactly what >>= does for lists. 这正是>>=对列表的作用。

One thing that really helped me see the practical value of this was thinking about DFAs and NFAs. 真正帮助我看到其实用价值的一件事是考虑DFA和NFA。 You can imagine trivially writing a DFA in Haskell something like this: 你可以想象在Haskell中写一个像DFA这样的DFA:

data State = S1 | S2 | S3 | S4 | Q
data Input = A | B
transition :: State -> Input -> State
transition S1 A = S2
transition S1 B = S3
-- and so on...

Then we could just fold over input: 然后我们可以折叠输入:

 foldl transition S1 [A, A, B, B]

Now, how would we take this code and generalize it to NFAs? 现在,我们如何采用这些代码并将其推广到NFA? Well, the transition "function" for an NFA can be thought of as a non-deterministic computation. 那么,NFA的过渡“功能”可以被认为是非确定性计算。 So we define something like: 所以我们定义如下:

transition S1 A = [S1, S2]
transition S1 B = []

But now we'd have to do some weird gymnastics to use foldl ! 但是现在我们必须做一些奇怪的体操才能使用foldl Happily, we can just foldM instead. 令人高兴的是,我们可以foldM代替。 So here the "computation" modeled by the monad is the non-deterministic transition function. 所以这里由monad建模的“计算”是非确定性转换函数。

Perhaps =<< is easier to understand from a computation point of view (it's just flip (>>=) ). 或许=<<从计算的角度来看更容易理解(它只是flip (>>=) )。 It has typing (=<<) :: (Monad m) => (a -> mb) -> ma -> mb , and corresponds to the type of function application, cf ($) :: (a -> b) -> a -> b . 它有输入(=<<) :: (Monad m) => (a -> mb) -> ma -> mb ,并且对应于函数应用程序的类型,cf ($) :: (a -> b) -> a -> b So >>= is just flipped function application on the monadic level. 所以>>=只是在monadic级别上翻转函数应用程序。

Also, (>>=) is used in desugaring do notation, and do syntactically corresponds very much to imperative code (in a suitable monad). 此外, (>>=)用于desugaring do notation,并且do语法上非常符合命令式代码(在合适的monad中)。

Here's a rough idea of how it works out as a model of computation: A type constructor M with an instance of Monad represents a parametric data structure, and the non-parametric parts of that structure can contain other information. 以下是关于它如何作为计算模型的粗略概念:具有Monad实例的类型构造函数M表示参数化数据结构,并且该结构的非参数部分可以包含其他信息。 The return and join correspond to some sort of monoid for those parts of the structure. returnjoin对应于结构的那些部分的某种幺半群。 A function a -> M b introduces information in that structure, based on an input of type a . 函数a -> M b基于类型a的输入在该结构中引入信息。 So by lifting a function a -> M b to M a -> M b we're using the parametric information of M to create some non-parametric information, then combining that with the information already present in a value of type M a . 因此,通过提升功能a -> M bM a -> M b ,我们正在使用的参数信息M创建一些非参数信息,然后结合,与已经存在于类型的值的信息M a

The asymmetric nature of the type a -> M b gives an inherent direction to the flow of non-parametric information, while the associativity requirement means that the overall order is the only thing that matters. 类型a -> M b的非对称性质给出了非参数信息流的固有方向,而相关性要求意味着整体顺序是唯一重要的。

The end result is augmenting functions with some sort of context that has a built-in notion of cause-and-effect. 最终结果是通过某种具有内在因果概念的上下文来增强函数。

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

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