什么是Monad?

最近比较巧合的接触了Functional programming,经常接触到Monad的概念(在Haskell和F#中),然而除了知道这个概念很难明白以外其他都不太清楚,粗略地看了几篇相关文章讲的很晦涩难懂。所以决定花一段时间将这个FP的奇怪概念搞个明白。

什么是monad?

wikipedia

太长了,懒得看

恩我也是这么想的。。所以我一直没看,我在这里尝试着来用简单一些的文字说明一下Monad的概念,有可能每段都有错误,谨慎啊。

Monad是一个FP中的专有名词,是一个含有变量的类,monad是Monad这个类的实例。这个类的作用是把一系列操作连接在一起。没错你可能想到do关键字,比如最简单的IO实例:

do

putStrLn "What is your name?"

name <- getLine

putStrLn ("Welcome, " ++ name ++ "!")

在这里do之后的list里面我们做了三件事情,打印,读取IO输入,输出结果。在这三句话中间传递了参数name,这是一个state,这个state存在的意义是使得我们能够以pure functional的方式执行这三句话。

然而do关键字是一个语法糖,在这个语法糖的背后是一个>>=,也就是bind操作符在支持这个操作的连续性。那么在这里说,Monad就是一个支持>>=操作符的类,就是一个能够连接多个operation的类。没错刚才那段代码就是一个monad,就是一段由几个function组成的control flow,bind操作符的作用就是读取左边操作的结果并且让右边操作能够使用。很简单

这说的太简单了,肯定是错的

是说的很简单,但是不能说是错的吧。。举个例子,假如有个没有概念的人问你“什么是函数”,怎么回答?一段映射?读取几个参数输出几个参数的代码?这么简单的概念你怎么使别人相信函数在编程过程中的重要作用呢,很难吧。Java中我们当然可以写一个函数,读取一个int返回一个int,这严格的遵守了数学函数的定义,但是同时这个函数还可以做很多其他的事情,比如打印出来,比如进一个死循环,但是这些不是pure function能做的,haskell这样的语言中function就应该读取一个值返回一个值,它对程序的影响只能体现在它的返回值上。“什么是函数”这个问题的答案大概长什么样子我想大家心里应该有数。

Monad能发挥巨大作用,不是因为它的定义太复杂,是因为他不只是简单的定义,而是可以延伸出无数个种类。没错bind操作符的确就是简单地把参数从左边传给右边,能包含bind操作符的都是monad,但是monad还可以同时做很多其他的事情,做的事情不一样monad的作用也不一样。换句话说,不同monad赋予了>>=不同的意义。

举一个不是我想出来的例子,以下代码是javascript的几个函数。

var sine = function(x) { return Math.sin(x) };

var cube = function(x) { return x * x * x };

var sineCubed = cube(sine(x));

sineCubed是一个组合函数,可以很简单的把它在Haskell中写出来。然而这个时候我们队sineCubed有个特殊的要求,要求它能够打印出来自己运行时的值。javascript中间在函数里加一句console.log即可,但是Haskell中间呢?没办法在sine:: Number -> Number这个函数中间加一句打印,那样违反了pure function的原则。那么我们只能在返回值中体现出来:

var sine = function(x) {

return [Math.sin(x), 'sine was called.'];

};

var cube = function(x) {

return [x * x * x, 'cube was called.'];

};

那么sineCubed此时应该怎么写?假设还是之前的写法,那么我们会发现cube函数需要读取一个数组了,无法执行。这时候就需要多做一步处理,假设这个时候我在做一个作业,只要完成作业即可,那我可能就是sineCubed中cube只读取sine返回值的第一个参数,或者改变cube的签名为cube :: (Number,String) -> (Number,String)野蛮地完成任务(语言穿越了,只是为了表达意思)。这显然不是best practice,更优雅的方法是什么?没错这位同学答对了,就是对参数进行一个包装和解包装的工作,我们需要一个工具能够做这个事情。

首先需要一个unit,它读取一个Number,将这个number放在一个container里,返回一个(Number,String)。

// unit :: Number -> (Number,String)

var unit = function(x) { return [x, ''] };

unit函数使得原本简单的返回值可以被包装成包含了其他信息的值。然后在lift函数中我们用到了unit:

// lift :: (Number -> Number) -> (Number -> (Number,String))

var lift = function(f) {

return function(x) {

return unit(f(x));

};

};

lift的签名是读取一个函数,返回一个函数,它将一个简单函数"lift"到了一个包含了一个其他信息的函数。要做sineCubed,我们还需要一个函数能够组合几个函数,这是compose做的事情,它简单地把两个函数复合起来。可以想象还需要一个bind,它使得原本的sine和cube的函数签名能够被修改成想要的类型。这几个抽象的函数概念就组成了一个monad,实际上bind和unit就组成了一个monad,刚刚做的事情其实就是Haskell的Writer monad所做的事情。打开这个链接看看,应该你就能懂了。

这么说,monad其实是一种design pattern?

我个人觉得拿javascript去形容Haskell中的概念是一件容易误导人的事情,之前在一篇很长的blog中也看到说“不要用其他语言的思维去考虑函数式语言”。说monad是一种design pattern是有一定道理的,假设你在程序中需要一个函数接受一类输入,得到另外一类的输出,那么就要考虑用到bind和unit这样的函数,unit函数包装参数的类得到需要的另外一种类型,bind函数修改原函数使得它能够接受自己返回的函数类型。这样做的好处是可以在达到目的的同时,避免对原来的代码做出“毁灭性”的彻底修改。

然而之上说的只是一种monad类型,并不适用到全部范围。我们来看看其它几种monad:

  • 在一系列操作中,每一步都返回一个success/failure的标志,只有success才执行下一步,failure则自动终止。这是Failure Monad。

  • 将返回标志改成Exception的处理,这是Error Monad, Exception Monad,如何处理完全可以自定义。

  • 每一步返回多个结果,在下一步遍历这些结果,进行筛选或者处理,这是List Monad。

  • 每一步操作都是针对state的一个action,下一步操作只从上一步操作返回的world status得到信息进行操作,bind操作使得IO的side effects能够保证按顺序处理,这是I/O Monad。(这里说的太笼统,最好再看看I/O Monad的说明。。)
    更合适的说,Monad是一个通用的将各个函数作为组建搭建起来的“积木”,这个积木有两个基本部件"return"和">>=",而且这两个部件满足一些特定的组合性质,那么我就可以说我搭建的是一个monad:

  1. (return x) >>= f == f x
  2. m >>= return == m
  3. (m >>= f) >>= g == m >>= (\x -> f x >>= g)

orz...

这些事情,不用Monad当然也能做到,但是Monad的意义是使得达到这些目的的方法简单很多,只需要去定义>>=做什么事情即可达到目的。

打字好累。。stackoverflow的这个链接有很多大牛讲解了自己对Monad的理解,看完这篇日志之后再去看这个链接可能会稍微多理解一些东西(或者可以发现我说错了那麻烦告诉我一下=,=)

第一次在简书上写日志,markdown的设置弄了很久。。希望这篇日志能够对大家有点小帮助

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271

推荐阅读更多精彩内容