38

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

我阅读了这篇优秀的文章(Javascript 简介),并认为我完全理解了 Monads。然后我阅读了关于 Monads 的维基百科条目,看到了这个:

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

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

  1. Monad 的目的是获取具有不同输入和输出类型的函数并使其可组合。它通过使用单个 monadic 类型包装输入和输出类型来做到这一点。
  2. Monad 由两个相互关联的函数组成:bind 和 unit。Bind 接受一个不可组合的函数 f 并返回一个新函数 g,该函数接受单子类型作为输入并返回单子类型。g 是可组合的。unit 函数接受 f 期望的类型的参数,并将其包装在 monadic 类型中。然后可以将其传递给 g,或者传递给像 g 这样的函数的任何组合。

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

4

3 回答 3

29

你没有犯错。这里要理解的关键思想是柯里化——两个参数的 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)

如文章所述,它将输入函数转换为易于组合的版本。(+)就像我之前解释的那样,这些是等价的。

于 2011-11-02T02:29:30.927 回答
22

请允许我推翻你对 Monads 的信念。我真诚地希望您意识到我并不是要无礼;我只是想避免刻薄的话。

Monad 的目的是获取具有不同输入和输出类型的函数并使其可组合。它通过使用单个 monadic 类型包装输入和输出类型来做到这一点。

不完全是。当你以“A Monad's purpose”开始一个句子时,你已经走错了路。单子不一定有“目的”。Monad只是一种抽象,一种适用于某些类型而不适用于其他类型的分类。抽象的目的Monad很简单,就是抽象。

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

是和不是。bind和的组合unit足以定义 Monad,但 、 和 的组合join同样fmap足够unit。事实上,后者是范畴论中描述 Monad 的典型方式。

Bind 接受一个不可组合的函数 f 并返回一个新函数 g,该函数接受单子类型作为输入并返回单子类型。

再次,不完全是。一元函数f :: a -> m b是完全可组合的,具有某些类型。g :: m b -> c我可以用get函数对它进行后期组合,或者我可以用getg . f :: a -> c函数预先组合它。h :: c -> af . h :: c -> m b

但是您完全正确地理解了第二部分:(>>= f) :: m a -> m b. 正如其他人所指出的,Haskell 的bind函数以相反的顺序接受参数。

g 是可组合的。

嗯,是。如果,那么您可以使用getg :: m a -> m b函数预先编写它,或者您可以使用 get 函数对其进行后期编写。请注意,这可能是Monad的形式。我想当你说“可组合”时,你的意思是说“你可以组合任意长的这种形式的函数链”,这是真的。f :: c -> m ag . f :: c -> m bh :: m b -> ch . g :: m a -> cc m vm

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

一种迂回的说法,但是是的,那是正确的。

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

再次,是的。虽然调用unit(或在 Haskell 中return)通常不是惯用的 Haskell,然后将其传递给(>>= 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
于 2011-11-02T06:47:09.333 回答
11

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

第一件事是我颠倒了的定义bind并将其写为“绑定”这个词,而它通常写为 operator >>=。所以bind f x通常写为x >>= f

因此,Haskellbind接受一个包含在 monad 中的值,并返回一个函数,该函数接受一个函数,然后用提取的值调用它。我可能使用了不精确的术语,所以使用代码可能会更好。

你有:

sine x = (sin x,     "sine was called.")
cube x = (x * x * x, "cube was called.")

现在,翻译你的 JS 绑定(Haskell 会自动进行柯里化,所以调用bind f会返回一个带元组的函数,然后模式匹配负责将其解包到xands中,我希望这是可以理解的):

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

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

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

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

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

现在重命名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."]

希望这会有所帮助,而不是引入更多的混乱——关键是这两个绑定定义是等价的,只是调用语法不同。

于 2011-11-02T02:58:54.643 回答