Haskell Monad绑定运算符混淆

时间:2011-11-02 01:59:58

标签: haskell monads composition currying function-composition

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

我读了这篇优秀的文章(an introduction in Javascript),并认为我完全理解了Monads。然后我读了Monads上的维基百科条目,看到了:

  

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

好的,在我引用的文章中,bind是一个仅使用一个参数的函数。维基百科说两个。我想到的我对Monads的理解如下:

  1. Monad的目的是使用具有不同输入和输出类型的函数并使其可组合。它通过使用单个monadic类型包装输入和输出类型来实现此目的。
  2. Monad由两个相互关联的函数组成:bind和unit。绑定采用不可组合的函数f并返回一个新函数g,它接受monadic类型作为输入并返回monadic类型。 g是可组合的。 unit函数接受f期望的类型的参数,并将其包装在monadic类型中。然后可以将其传递给g,或者传递给任何函数组合,例如g。
  3. 但是肯定有些错误,因为我的bind概念需要一个参数:一个函数。但是(根据维基百科)Haskell的绑定实际上需要两个参数!我的错误在哪里?

3 个答案:

答案 0 :(得分:25)

你没犯错误。这里理解的关键思想是currying - 两个参数的Haskell函数可以通过两种方式看到。第一个只是两个参数的函数。例如,如果你有(+),这通常被视为带两个参数并添加它们。另一种看待它的方式是作为一个额外的机器生产者。 (+)是一个函数,它接受一个数字,比如x,并创建一个将添加x的函数。

(+) x = \y -> x + y
(+) x y = (\y -> x + y) y = x + y

在处理monad时,有时可能更好,就像上面提到的那样,考虑=<< >>=的翻转版本。有两种方法可以解决这个问题:

(=<<) :: (a -> m b) -> m a -> m b

是两个参数的函数,

(=<<) :: (a -> m b) -> (m a -> m b)

将输入函数转换为易于撰写的版本,如上所述。这些与我之前解释过的(+)相同。

答案 1 :(得分:20)

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

  

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

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

  

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

是和否。 bindunit的组合足以定义Monad,但joinfmapunit的组合同样足够。事实上,后者是Monads通常在类别理论中描述的方式。

  

Bind接受一个不可组合的函数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是Monad。我想当你说“可组合”时,你的意思是说“你可以任意构成这种形式的任意长链函数”,这是真的。

  

单位函数采用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

答案 2 :(得分: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.")

现在,翻译你的JS绑定(Haskell自动进行currying,所以调用bind f返回一个带有元组的函数,然后模式匹配负责将它解压缩到x和{{1} },我希望这是可以理解的):

s

你可以看到它有效:

bind f (x, s) = (y, s ++ t)
                where (y, t) = f x

现在,让我们反驳*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.")

的论点
bind

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

bind' (x, s) f = (y, s ++ t)
                 where (y, t) = f x

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

*Main> bind' (bind' (3, "") cube) sine
(0.956375928404503,"cube was called.sine was called.")

现在将*Main> (3, "") `bind'` cube `bind'` sine (0.956375928404503,"cube was called.sine was called.") 重命名为bind'>>=),您就可以获得所需内容。如您所见,通过此定义,您可以有效地摆脱单独的合成运算符。

将新东西翻译成JavaScript会产生类似这样的东西(再次注意,我只是颠倒了参数顺序):

(3, "") >>= cube >>= sine

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