图解 Monad

作者: 阮一峰

日期: 2015年7月16日

函数式编程有一个重要概念,叫做Monad

网上有很多解释(这里这里),但都很抽象,不容易看懂。我尝试了好多次,还是不明白Monad到底是什么。

昨天,我读到了Aditya Bhargava的文章,他画了很多图。我想了半天,终于恍然大悟。下面,我就用这些图来解释Monad。

1.

软件最基本的数据,就是各种值(value)。

2.

处理值的一系列操作,可以封装成函数。输入一个值,会得到另一个值。上图的"(+3)"就是一个函数,对输入的值加上3,再输出。

3.

函数很像漏斗,上面进入一个值,下面出来一个值。

4.

函数可以连接起来使用,一个函数接着另一个函数。

5.

函数还可以依次处理数据集合的每个成员。

6.

说完了函数,再来看第二个概念:数据类型(type)。

数据类型就是对值的一种封装,不仅包括值本身,还包括相关的属性和方法。上图就是2的封装,从此2就不是一个单纯的值,而是一种数据类型的实例,只能在数据类型的场景(context)中使用。

7.

2变成数据类型以后,原来的函数就不能用了。因为"(+3)"这个函数是处理值的(简称"值函数"),而不是处理数据类型的。

8.

我们需要重新定义一种运算。它接受"值函数"和数据类型的实例作为输入参数,使用"值函数"处理后,再输出数据类型的另一个实例。上图的fmap就代表了这种运算。

9.

fmap的内部,实际上是这样:打开封装的数据类型,取出值,用值函数处理以后,再封装回数据类型。

10.

一个有趣的问题来了。如果我们把函数也封装成数据类型,会怎样?

上图就是把函数"(+3)"封装成一种数据类型。

11.

这时,就需要再定义一种新的运算。它不是值与值的运算,也不是值与数据类型的运算,而是数据类型与数据类型的运算。

上图中,两个数据类型进行运算。首先,取出它们各自的值,一个是函数,一个是数值;然后,使用函数处理数值;最后,将函数的返回结果再封装进数据类型。

12.

函数可以返回值,当然也可以返回数据类型。

13.

我们需要的是这样一种函数:它的输入和输出都是数据类型。

14.

这样做的好处是什么?

因为数据类型是带有运算方法的,如果每一步返回的都是数据类型的实例,我们就可以把它们连接起来。

15.

来看一个实例,系统的I/O提供了用户的输入。

16.

getLine函数可以将用户的输入处理成一个字符串类型(STR)的实例。

17.

readfile函数接受STR实例当作文件名,返回一个文件类型的实例。

18.

putStrLn函数将文件内容输出。

19.

所有这些运算连起来,就叫做Monad。

简单说,Monad就是一种设计模式,表示将一个运算过程,通过函数拆解成互相连接的多个步骤。你只要提供下一步运算所需的函数,整个运算就会自动进行下去。

(完)

留言(52条)

佩服佩服,简单易懂。

都说Monad可以简化IO操作,没看出来什么关系!

很生动形象。看懂一半,稍后再多看几遍。

@corper,谁告诉你Monad可以简化IO操作的? Monad最大的作用就是在pure 函数式编程中处理副作用。你见过C,JAVA之类的命令式语言提过Monad的概念吗?只有Haskell,只要学Haskell就逃不了理解Monad,因为它是pure的。

引用genesis的发言:

@corper,谁告诉你Monad可以简化IO操作的? Monad最大的作用就是在pure 函数式编程中处理副作用。你见过C,JAVA之类的命令式语言提过Monad的概念吗?只有Haskell,只要学Haskell就逃不了理解Monad,因为它是pure的。

其他语言也可以是现实monad其实

最重要的就是conclusion。。。然后你没有放过来

而且真正图解monad部分的马桶搋子也没有放过来。。。结果绝大部分篇幅讲functor和Applicative。

不过感谢你给了原文的link

还有。。。
在15点你说
“来看一个实例,用户输入一个值10。”
其实那根本是IO...

@cttet,

确实看错了,已经改过来了,谢谢提醒。

目前没有共鸣,不知道这个有什么用处。

原文是好文章啊,但还是浅了。

PS:图片中的 Just 是指 Haskell 中的 Just type constructor。

我看文章,一直不知道3为什么不是数据类型,看了原文才知道,不是“数据类型”,而是“maybe数据类型”。

我也在學習相關的內容,建議大家可以看看這兩個slide:

Railway Oriented Programming
http://fsharpforfunandprofit.com/rop/

Functional Programming Design Patterns
http://fsharpforfunandprofit.com/fppatterns/

函数式的纯洁性在于对于一个特定的参数,必定存在一个特定的返回值。所以函数式的执行是无关顺序的。
而在现实世界里,很多东西就必须是有顺序的,比如输入输出。于是Haskell决定用monad去模拟这种操作。monad来源于范畴论,他并不解决副作用问题本身,而是对此类问题提供了抽象。

“数据类型”太让人昏了。原文中是把数值封装到Maybe这样的特殊数据类型里。丢了Maybe,跟“数据类型”相关的都不太好理解了。

非常感谢,这篇文章终于让我明白了monad。之前找了很多文章,也一直未明白,不过这个monad的学习之前,有一个cps的概念可能会更好一些。

我觉得你下一步是想要去描述promise类似的实现了。

看了哈,还是没看懂
一直认为函数式编程的思想就是把函数当做另外一个函数的参数来使用,不知道错多远了:)

谈谈monad,可以想象promise,在异步情况下的使用,链接各种函数,然后得到结果。

如果没有promise,你也可以用各种callback做。

对于javascript, async, await是不是类似于haskell的do syntax呢?

很棒的文章。

您好,我曾经在读书公园的联系我们页面中给您发过关于注册问题的邮件,一直未能得到回复。

不知是否发送有误。

难道不就是UNIX管道吗?

monad 意思就是单阶的,跟UNIX管道意义上一样,只是在具体上更抽象。
前文的 chaining operations 这个词解释的很到位。
实际中,请把 monad 当作 Interger,String 之类的,每个 chaining operation 是一个 instance 就对了。

引用cttet的发言:

还有。。。
在15点你说
“来看一个实例,用户输入一个值10。”
其实那根本是IO...



你看看网站字体有多重要吧....

感觉有点像管道啊,以前没听说过这个词啊,看来还得多学习啊。
图画得很有意思,有时间再看看原文。

引用cttet的发言:

最重要的就是conclusion。。。然后你没有放过来

Maybe 那部分也没有放进来,导致 Just 一下子很突兀。

看来阮老师现在也是一知半解呀。顿时找到自信了。

讲的完全不对呀。
好多次序都颠倒了。

原文写的真棒!

在译文中,列表、Just 和 都太突兀了。

坑爹啊。
原文末尾有中文译文的。

http://jiyinyiyong.github.io/monads-in-pictures/

感觉就是个装在盒子里的lambda,最好的例子其实是flatMap

比如

[1, 2, 3].flatMap( x => [x, x+1])

结果是

[1, 2, 2, 3, 3, 4]

生动形象,便于理解。比其他文章里枯燥乏味的冗长大论好太多了。

图示完全没帮助。完全看文字才能理解。

简单易懂

偏个题,单子在哲学里是个重要的概念,大致意思就是完整单个的个体却能反射出整个世界

阮大哥, 我也觉您对monad的理解有些偏差 . 这有一篇文章不错 : http://blog.sigfpe.com/2006/08/you-could-have-invented-monads-and.html

图是怎么画的

原来就是管道啊!把值或者函数封装传进下一个函数的意思?

还是不理解,如果只是为了连续操作,函数连续嵌套调用不就行了吗?不理解后面引出的“数据类型”是什么作用。

牛逼,简单易懂。
context instance = new context:
func -(context)dealContext:(context):content func:(func)func;

instance = [func dealContext:instance func:AddFunc];
instance = [func dealContext:instance func:SubFunc];

context instance = new context:
func monad1: -(context)monad1;
func monad2: -(context)monad1;
func monad3: -(context)monad1;

[instance monad1] monad2] monad3];

从博主的描述看monad只是个概念,其他语言使用都可以实现并且有相关的库(但不会提及这个术语),楼上提到的flatMap是一个经典使用场景。

这东西要实际写写 Haskell 才能完全理解,光看理论还是不会用。

使用图片有 Aditya Bhargava 的许可吗?

胡扯淡,你写过几行函数式?换个问题,你写过几行代码?

多次从作者的博客中受教,写得内容简洁且命中要害!

这篇文章并没有讲清楚这个概念。

不是应该从幺半群Monoid讲到Funtor再讲到Monad么,这篇文章我看了很多次都没理解,后来看的scala函数式编程这本书

引用lizi的发言:

不是应该从幺半群Monoid讲到Funtor再讲到Monad么,这篇文章我看了很多次都没理解,后来看的scala函数式编程这本书

文章太好了,感谢,打算重头看一下,涨了不少见识,我觉得那些开源的人真的很伟大,站在巨人的肩膀上,激动又兴奋!

非常有帮助

Ruby 语言下的一个实现 https://dry-rb.org/gems/dry-monads/1.0/task/

不就是自函子范畴上的幺半群么,有什么难理解的(狗头表情)。

值,值函数打引号,数据类型----阮老师讲的这些概念,都没把一些人整糊涂,我也是醉成贵妃了。

我们站在haskell的环境中,首先明确数据类型是有值的,例如Int这个数据类型从数量上讲有无限个值,可能的值:3,7,999...

(+3)是不完全函数,它是某一种函数类型,它的输入和输出都有类型约束---Num这个类型类(Num类型类来自于haskell的概念),这个类型类有许多实例类型,如:Int,Float等,亦即,输入输出的数据类型可以是Num的实例类型。

冲(+3)函数是用来处理值的,和“值函数”私加引号这些上下文语句,阮老师所讲的值函数应该是普通意义上“值函数”,亦即,函数在运行时就是用来处理值的,而(+3)可能处理的是Int类型的值。

然而,值函数打引号,也可能指的是只狗造函数。(概念来自于haskell)

引用corper的发言:

都说Monad可以简化IO操作,没看出来什么关系!

IO 只是 Monad 的一个种类,还有很多种 Monad。函数式中的 Monad 其实隐含包括了惰性求值的概念。 https://typelevel.org/cats-effect/

我看了好几遍没看懂,现在 我懂了。。。。大神就是大神

好文,通过本文理解了大学老师讲过的装箱拆箱概念...谢...

我要发表看法

«-必填

«-必填,不公开

«-我信任你,不会填写广告链接