标 题: 什么是Monad(1): introduction
发信站: 水木社区 (Sun Oct 8 05:06:47 2006), 站内
在函数式编程里面,Monad是一个门槛。但是要解释“什么是monad”,这世界上没几个人
能一下讲清楚,因为涉及的背景知识太复杂。
介绍Monad可以被认为是一个"industry job":如果你能一下说清楚,就可以去当教授。
下面我说说我个人的从程序员角度的理解,目的不在于介绍monad的技术细节,而在于介
绍monad的背景和相关知识环境。
(基本概念:combinator/combinator programming/combinator language)
首先,函数式编程可以让我们方便的使用高阶函数(higher-order functions)写出很通用
的程序部件,比如map, fold, filter, \x->[x], 等等,这些函数组合性质和重用性都很
好,里面没有free variable,可以把这样的函数叫做"combinator"。
在程序里大量使用combinator就产生了一种编程风格,叫做"combinator programming"。
这种编程风格对于给定的问题定义几个combinator,然后拿这几个combinator当作一个“
语言”,把它们组合起来写程序。我们把这样由combinator构成的语言叫做"combinator
language"。
下面举几个combinator language例子:
例子1: 用map/fold/join/filter/unit这几个基本函数组合起来就可以写出很复杂的list
处理程序。
例子2: 函数式编程的状态经常要作为参数传递很麻烦, 就有人定义了所谓的"state
monad", 用几个基本的combinator部件来处理状态: return, bind, get, put
例子3: 一个很常见的应用就是所谓的"combinator parsing". 每一个基本的combinator
就是一个简单的parser, 只负责parse基本的语法单位, 比如我定义一个函数"word"来
parse一个词, 定义一个函数"number"来parse一个数字, 再定义一个函数"then"来组合各
种parser. 然后我就可以通过组合这些基本的函数来构造复杂的parser: "word then
number".
例子4: 最极端的例子是SK combinators,这两个函数组合起来跟图灵机等价,只用S和K
就可以编出一切程序.
(Generic combinator interfaces)
Combinator programming就像搭积木,用一些函数构造基本的积木块,再用一些组合性质
比较好的函数来连接各个模块,就像LEGO积木一样。人们编程经验很多以后,就总结出一
些基本的通用的设计模式,这些通用模式在很多combinator language中都适用。下面
要提到的Monad就是其中一个。
(Monad, monadic language)
Monad是一个通用的combinator interface。到底什么算是一个monad呢?如果我定义了一
个combinator language,里面有两个基本部件"return"和">>=",而且这两个部件满足以
下的组合性质,就可以说这个combinator language是一个monad:
1. (return x) >>= f == f x
2. m >>= return == m
3. (m >>= f) >>= g == m >>= (\x -> f x >>= g)
这两个基本部件的意义其实很简单:return构造一个基本的"积木块",而(>>=)把各个积
木块顺序连接起来。如果一个combinator language里面有这样的基本结构,就可以说它
是一个monad,可以说这个language是一个monadic language。
由于monad有很好的组合性质,可以很容易的表达程序的控制结构,它成为很多
combinator language的设计方案。具体的例子很多,比如identity monad, maybe
monad, state monad, CPS monad,monadic parser combinators, 等等,都是经典的
monadic language。如果我想设计一个combinator language,首先就会想能不能设计
成一个monad。
(Monadic programming style)
如果我们设计的combinator language是一个monad,编出来的程序都会有一种特定的风格
:用return构造基本的积木块,用(>>=)组合各个模块。比如说,在Haskell的IO monad里
面我们可以写这么一段程序:
return stdin >>= (\h1 -> return stdout >>= (\h2 -> hGetChar h1 >>= (\x -> hPutChar h2 x >>= (\_ -> hPutChar h2 x ))))
实现的功能就是从stdin里面读入一个字符x,然后把它往stdout里面输出两次。可以看到
,通过使用return, >>=和匿名函数就可以实现复杂的variable binding。
(Monads in Haskell)
如果用其他的编程语言,上面的介绍就足够了。Haskell对Monad提供了更好的支持,可以
让基于Monad的编程更简单!具体有以下两点:
1. Type class。在Haskell里,可以把Monad定义成为一个type class,提供两个标准操
作:return和>>=。这样,不同的monad都可以重载这两个操作,让所有基于monad的程序
看上去都是相同的!
2. Do-syntax。既然type class可以让所有的monad程序都有相同的界面,只要让编译器
把这个界面化简一下,就相当于化简了所有monad界面。Haskell提供了一个"do-syntax"
,极大的简化了前一节提到的monadic programming style。比如说,前一节的程序可以
写为:
do { let h1 = stdin h2 = stdout x <- hGetChar h1 hPutChar h2 x hPutChar h2 x }
在编译的时候,这个使用do-syntax的程序会被翻译成前一节的基于return和>>=的程序。
现在看来,易读性已经和C/Java编程差不多了。。。