Haskell Monad 绑定运算符混淆

发布于 2024-12-12 22:48:28 字数 968 浏览 0 评论 0原文

好吧,我不是 Haskell 程序员,但我对 Haskell 背后的很多想法非常感兴趣,并且正在考虑学习它。但我陷入了第一个困境:我似乎无法理解 Monad,这似乎是相当基本的。我知道有上百万个问题要求解释 Monad,所以我将更具体地说明困扰我的问题:

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

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

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

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

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

Okay, so I am not a Haskell programmer, but I am absolutely intrigued by a lot of the ideas behind Haskell and am looking into learning it. But I'm stuck at square one: I can't seem to wrap my head around Monads, which seem to be fairly fundamental. I know there are a million questions on SO asking to explain Monads, so I'm going to be a little more specific about what's bugging me:

I read this excellent article (an introduction in Javascript), and thought that I understood Monads completely. Then I read the Wikipedia entry on Monads, and saw this:

A binding operation of polymorphic type (M t)→(t→M u)→(M u), which Haskell represents by the infix operator >>=. Its first argument is a value in a monadic type, its second argument is a function that maps from the underlying type of the first argument to another monadic type, and its result is in that other monadic type.

Okay, in the article that I cited, bind was a function which took only one argument. Wikipedia says two. What I thought I understood about Monads was the following:

  1. A Monad's purpose is to take a function with different input and output types and to make it composable. It does this by wrapping the input and output types with a single monadic type.
  2. A Monad consists of two interrelated functions: bind and unit. Bind takes a non-composable function f and returns a new function g that accepts the monadic type as input and returns the monadic type. g is composable. The unit function takes an argument of the type that f expected, and wraps it in the monadic type. This can then be passed to g, or to any composition of functions like g.

But there must be something wrong, because my concept of bind takes one argument: a function. But (according to Wikipedia) Haskell's bind actually takes two arguments! Where is my mistake?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

幼儿园老大 2024-12-19 22:48:30

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

第一件事是我翻转了 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 会自动柯里化,因此调用 bind f 返回一个接受元组的函数,然后模式匹配负责将其解包到 x 和 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

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

*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 >>=正弦),你就得到了你想要的东西。正如您所看到的,通过这个定义,您可以有效地摆脱单独的组合运算符。

将新事物翻译回 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."]

希望这会有所帮助,并且不会引入更多混乱 - 重点是这两个绑定定义是等效的,只是调用不同句法。

The article you link is based on sigfpe's article, which uses a flipped definition of bind:

The first thing is that I've flipped the definition of bind and written it as the word 'bind' whereas it's normally written as the operator >>=. So bind f x is normally written as x >>= f.

So, the Haskell bind takes a value enclosed in a monad, and returns a function, which takes a function and then calls it with the extracted value. I might be using non-precise terminology, so maybe better with code.

You have:

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

Now, translating your JS bind (Haskell does automatic currying, so calling bind f returns a function that takes a tuple, and then pattern matching takes care of unpacking it into x and s, I hope that's understandable):

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

You can see it working:

*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.")

Now, let's reverse arguments of bind:

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

You can clearly see it's still doing the same thing, but with a bit different syntax:

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

Now, Haskell has a syntax trick that allows you to use any function as an infix operator. So you can write:

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

Now rename bind' to >>= ((3, "") >>= cube >>= sine) and you've got what you were looking for. As you can see, with this definition, you can effectively get rid of the separate composition operator.

Translating the new thing back into JavaScript would yield something like this (notice that again, I only reverse the argument order):

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."]

Hope this helps, and not introduces more confusion — the point is that those two bind definitions are equivalent, only differing in call syntax.

等待我真够勒 2024-12-19 22:48: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)

输入函数转换为本文提到的易于组合的版本。正如我之前所解释的,它们与 (+) 是等效的。

You are not making a mistake. The key idea to understand here is currying - that a Haskell function of two arguments can be seen in two ways. The first is as simply a function of two arguments. If you have, for example, (+), this is usually seen as taking two arguments and adding them. The other way to see it is as a addition machine producer. (+) is a function that takes a number, say x, and makes a function that will add x.

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

When dealing with monads, sometimes it is probably better, as ephemient mentioned above, to think of =<<, the flipped version of >>=. There are two ways to look at this:

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

which is a function of two arguments, and

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

which transforms the input function to an easily composed version as the article mentioned. These are equivalent just like (+) as I explained before.

七七 2024-12-19 22:48:29

请允许我推翻你对 Monad 的信念。我真诚地希望你意识到我并不是想表现得无礼;我只是想表现得无礼。我只是想避免拐弯抹角。

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

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

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

是的,也不是。 bindunit 的组合足以定义 Monad,但是 joinfmapunit 同样足够。事实上,后者是范畴论中典型描述单子的方式。

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

再说一遍,不完全是。一元函数 f :: a -> m b 对于某些类型来说是完全可组合的。我可以使用函数 g :: mb -> 对其进行后合成c 得到 g 。 f::a-> c,或者我可以用函数 h :: c -> 预先组合它。 a 得到 f 。 h::c-> m b

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

g 是可组合的。

嗯,是的。如果 g :: ma -> m b,那么你可以用函数 f :: c -> 来预组合它。 m a 得到 g 。 f::c-> m b,或者您可以使用函数 h :: mb -> 进行后合成。 c 得到 h 。 g::ma-> c.请注意,c可以采用m v形式,其中m是一个Monad。我想当你说“可组合”时,你的意思是说“你可以组合这种形式的任意长的函数链”,这是正确的。

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

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

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

再说一次,是的。尽管调用 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

Allow me to tear down your beliefs about Monads. I sincerely hope you realize that I am not trying to be rude; I'm simply trying to avoid mincing words.

A Monad's purpose is to take a function with different input and output types and to make it composable. It does this by wrapping the input and output types with a single monadic type.

Not exactly. When you start a sentence with "A Monad's purpose", you're already on the wrong foot. Monads don't necessarily have a "purpose". Monad is simply an abstraction, a classification which applies to certain types and not to others. The purpose of the Monad abstraction is simply that, abstraction.

A Monad consists of two interrelated functions: bind and unit.

Yes and no. The combination of bind and unit are sufficient to define a Monad, but the combination of join, fmap, and unit is equally sufficient. The latter is, in fact, the way that Monads are typically described in Category Theory.

Bind takes a non-composable function f and returns a new function g that accepts the monadic type as input and returns the monadic type.

Again, not exactly. A monadic function f :: a -> m b is perfectly composable, with certain types. I can post-compose it with a function g :: m b -> c to get g . f :: a -> c, or I can pre-compose it with a function h :: c -> a to get f . h :: c -> m b.

But you got the second part absolutely right: (>>= f) :: m a -> m b. As others have noted, Haskell's bind function takes the arguments in the opposite order.

g is composable.

Well, yes. If g :: m a -> m b, then you can pre-compose it with a function f :: c -> m a to get g . f :: c -> m b, or you can post-compose it with a function h :: m b -> c to get h . g :: m a -> c. Note that c could be of the form m v where m is a Monad. I suppose when you say "composable" you mean to say "you can compose arbitrarily long chains of functions of this form", which is sort of true.

The unit function takes an argument of the type that f expected, and wraps it in the monadic type.

A roundabout way of saying it, but yes, that's about right.

This [the result of applying unit to some value] can then be passed to g, or to any composition of functions like g.

Again, yes. Although it is generally not idiomatic Haskell to call unit (or in Haskell, return) and then pass that to (>>= 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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文