Haskell Monad绑定运算符混淆

Ord*_*Ord 32 monads haskell currying composition function-composition

好吧,所以我不是Haskell程序员,但我对Haskell背后的许多想法非常感兴趣,并且我正在研究它.但是我被困在第一个方面:我似乎无法绕过Monads,这似乎是相当基础的.我知道有一百万个关于SO的问题要求解释Monads,所以我会更加具体地说明了什么在困扰我:

我读了这篇优秀的文章(Javascript中的介绍),并认为我完全了解Monads.然后我读了Monads上的维基百科条目,看到了:

多态类型(M t)→(t→M u)→(M u)的绑定操作,其中Haskell由中缀运算符表示>> =.它的第一个参数是monadic类型的值,它的第二个参数是一个函数,它从第一个参数的基础类型映射到另一个monadic类型,其结果是在其他monadic类型中.

好的,在我引用的文章中,bind是一个仅占用一个参数的函数.维基百科说两个.我认为我对Monads的理解如下:

  1. Monad的目的是使用具有不同输入和输出类型的函数并使其可组合.它通过使用单个monadic类型包装输入和输出类型来实现此目的.
  2. Monad由两个相互关联的函数组成:bind和unit.绑定采用不可组合的函数f并返回一个新函数g,它接受monadic类型作为输入并返回monadic类型.g是可组合的.unit函数接受f期望的类型的参数,并将其包装在monadic类型中.然后可以将其传递给g,或者传递给g之类的任何函数组合.

但是肯定有一些错误,因为我的bind概念需要一个参数:一个函数.但是(根据维基百科)Haskell的绑定实际上有两个参数!我的错误在哪里?

ger*_*ter 25

你没犯错.理解这里的关键思想是currying - 两个参数的Haskell函数可以用两种方式看出来.第一个只是两个参数的函数.例如,如果您有(+)这种情况,通常会将其视为两个参数并添加它们.另一种看待它的方式是作为一个额外的机器生产者.(+)是一个函数,它接受一个数字,比如说x,并创建一个将要添加的函数x.

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

在处理单子时,有时可能更好,就像上面提到的那样,可以想到=<<,翻版的>>=.有两种方法可以看出这个:

(=<<) :: (a -> m b) -> m a -> m b
Run Code Online (Sandbox Code Playgroud)

这是两个参数的函数,和

(=<<) :: (a -> m b) -> (m a -> m b)
Run Code Online (Sandbox Code Playgroud)

如上所述,它将输入功能转换为易于组合的版本.这些就像(+)我之前解释的一样.

  • 所以`x >> = f`(或`f = << x`)可以理解为:取f,包装它以便接受monadic类型,然后用参数x调用包装函数? (2认同)

Dan*_*ton 20

请允许我推翻你对Monads的看法.我真诚地希望你意识到我并不是想要粗鲁; 我只是想避免说话.

Monad的目的是使用具有不同输入和输出类型的函数并使其可组合.它通过使用单个monadic类型包装输入和输出类型来实现此目的.

不完全是.当你用"Monad的目的"开始一个句子时,你已经走错了路.Monads不一定有"目的".Monad只是一种抽象,一种适用于某些类型而不适用于其他类型的分类.Monad抽象的目的只是抽象.

Monad由两个相互关联的函数组成:bind和unit.

是的,不是.的组合bindunit是足以限定一个单子,但组合join,fmapunit同样是足够的.事实上,后者是Monads通常在类别理论中描述的方式.

绑定采用不可组合的函数f并返回一个新函数g,它接受monadic类型作为输入并返回monadic类型.

再次,不完全是.monadic函数f :: a -> m b是完全可组合的,具有某些类型.与功能,我可以张贴,组成它g :: m b -> c得到g . f :: a -> c,或用功能我可以预先撰写它h :: c -> a得到f . h :: c -> m b.

但你的第二部分绝对正确:(>>= f) :: m a -> m b.正如其他人所指出的那样,Haskell的bind函数以相反的顺序获取参数.

g是可组合的.

嗯,是.如果g :: m a -> m b,那么你就可以用一个函数预先撰写它f :: c -> m a得到g . f :: c -> m b,或用功能,您可以发布,撰写它h :: m b -> c得到h . g :: m a -> c.请注意,c 可能是这样的形式m v,其中m是一个单子.我想当你说"可组合"时,你的意思是说"你可以任意构成这种形式的任意长链函数",这是真的.

unit函数接受f期望的类型的参数,并将其包装在monadic类型中.

一种迂回的说法,但是,这是正确的.

然后,这[应用于unit某个值的结果]可以传递给g,或者传递给g之类的任何函数组合.

再次,是的.虽然通常不是惯用的Haskell来调用unit(或者在Haskell中return),然后将其传递给(>>= f).

-- instead of
return x >>= f >>= g
-- simply go with
f x >>= g

-- instead of
\x -> return x >>= f >>= g
-- simply go with
f >=> g
-- or
g <=< f
Run Code Online (Sandbox Code Playgroud)

  • 好吧,我可能会想出一个用例来回答我自己的问题:假设我们有一堆函数,每个函数都接受一个值(无论是字符串,数字,等等)作为输入并返回值列表(不一定是同一类型).如果我们想将一对多函数的结果传递给另一个一对多函数,我们通常必须解包第一个函数返回的列表,将第二个函数应用于每个值,然后连接结果.monad可以抽象出所有的样板.这是一个有效的例子吗? (5认同)
  • Ord:这是一个很好的例子,而且正是Monad的List实例的定义.`return x = [x]`; `xs >> = f = concatMap f xs`.(警告:另外,可能会引起混乱的思考)concatMap将函数`a - > [b]`映射到`a`列表,然后将结果列表从'[[b]]`"展平"到`[b ]`.`concatMap f xs = concat(map f xs)`.这是(并非巧合)类似于`bind`可以用`fmap`和`join`来定义*any*monad:`x >> = f = join(fmap fx)`; 你可以看到列表的`join = concat`和`fmap = map`. (2认同)

Cat*_*lus 9

您链接的文章基于sigfpe的文章,该文章使用了bind的翻转定义:

第一件事是我翻了定义bind并将其写成'bind'这个词,而它通常写成运算符>>=.所以bind f x通常写成x >>= f.

因此,Haskell bind获取一个封装在monad中的值,并返回一个函数,该函数接受一个函数,然后用提取的值调用它.我可能会使用非精确的术语,所以使用代码可能会更好.

你有:

sine x = (sin x,     "sine was called.")
cube x = (x * x * x, "cube was called.")
Run Code Online (Sandbox Code Playgroud)

现在,您的翻译JS绑定(哈斯克尔做自动钻营,因此调用bind f返回,需要一个元组,然后模式匹配需要拆开包装成的保健功能xs,我希望这是可以理解的):

bind f (x, s) = (y, s ++ t)
                where (y, t) = f x
Run Code Online (Sandbox Code Playgroud)

你可以看到它的工作原理:

*Main> :t sine
sine :: Floating t => t -> (t, [Char])
*Main> :t bind sine
bind sine :: Floating t1 => (t1, [Char]) -> (t1, [Char])
*Main> (bind sine . bind cube) (3, "")
(0.956375928404503,"cube was called.sine was called.")
Run Code Online (Sandbox Code Playgroud)

现在,让我们反驳一下bind:

bind' (x, s) f = (y, s ++ t)
                 where (y, t) = f x
Run Code Online (Sandbox Code Playgroud)

你可以清楚地看到它仍在做同样的事情,但语法有点不同:

*Main> bind' (bind' (3, "") cube) sine
(0.956375928404503,"cube was called.sine was called.")
Run Code Online (Sandbox Code Playgroud)

现在,Haskell有一个语法技巧,允许您将任何函数用作中缀运算符.所以你可以写:

*Main> (3, "") `bind'` cube `bind'` sine
(0.956375928404503,"cube was called.sine was called.")
Run Code Online (Sandbox Code Playgroud)

现在重命名bind'>>=((3, "") >>= cube >>= sine),你就得到了你想要的东西.如您所见,通过此定义,您可以有效地摆脱单独的合成运算符.

将新事物转换回JavaScript会产生这样的结果(再次注意,我只反转参数顺序):

var bind = function(tuple) {
    return function(f) {
        var x  = tuple[0],
            s  = tuple[1],
            fx = f(x),
            y  = fx[0],
            t  = fx[1];

        return [y, s + t];
    };
};

// ugly, but it's JS, after all
var f = function(x) { return bind(bind(x)(cube))(sine); }

f([3, ""]); // [0.956375928404503, "cube was called.sine was called."]
Run Code Online (Sandbox Code Playgroud)

希望这有所帮助,而不是引入更多的混淆 - 重点是这两个绑定定义是等效的,只是在调用语法上有所不同.