Monad是Haskell一个比较受争议的特性,看到网上黑它的人真不少,也不乏自称非常熟悉Haskell和其他语言的“大神”,他们称Haskell过分强调纯函数和Monad是一种偏执,是多此一举。但是,我是力挺Monad的。

首先说纯函数,Haskell强迫使用纯函数是有道理的,当然官方给出的理由是方便程序员调试,因为你能轻松区分有副作用的函数和无副作用的函数,就能快速知道某一类错误会在哪些函数里发生,在哪些函数不会发生。第一,这个理由是充分的,作为一个“有态度”的语言,谷歌的Go语言也做出了很多让人不方便的限制,但从更长远意义上来看规范了人们的编程习惯,这么做并没有什么不对的。第二,从一个初学者的角度出发,强迫使用纯函数就强迫了初学者放弃原先命令式编程的思路,而使用函数式思想去实现一些目的。试想,如果Haskell允许你写循环,改状态,你还能那么快上手map、filter这些简单高效的函数吗?

纯函数确实有它的问题,因为我们确实有需要处理IO的时候。暂且不提IO,我们也需要全局变量,来在不同函数间传递信息。总是靠参数来传递信息不但冗余低效而且会让代码复杂不易于理解。确实,在弄明白Monad的工作原理之前,我曾为了一个随机数发生器伤透脑筋,因为传统的随机数发生器依赖一个全局的随机数种子,而在纯函数中没有全局变量,就不得不把这个种子当作参数传递给所有相关的函数,不但如此,每多一个类似的全局变量,就要多一个这样的参数,这种现象简直是丑陋地不可原谅!因此,抨击纯函数的人称Haskell为充满了电线的语言!时间一长,这些参数之间传来传去就会让程序变得复杂和臃肿,难以维护!

于是,Monad就出现了!如果说上面说的把全局变量作为参数传递是电线的话,Monad就是这些电线的电线盒,一个Monad可以把某个类型的全局变量放到盒子里藏起来,让你看起来像在用全局变量一样,不需要靠参数来传递,同时又保持了函数的纯性(Purity)。

Monad反对者这时候就说了:你看,这不是多此一举嘛!明明在用副作用,却非得用一个Monad包装起来,假装自己在写纯函数!

而我却想这么说:你写的每个函数都是纯函数,却可以靠Monad来实现原本需要副作用才能实现的效果!

我也稍微看过F#、OCaml这样的“不纯”的函数式编程语言,它们都是从编译器层面支持修改变量的值的。从实用的角度来讲,这很方便,没有抽象的Monad来增加初学者的学习难度。一方面,官方给出的Debug的问题确实存在,在这类不纯的语言中,遇到了一个问题,你不得不排查每一个函数,因为每个函数都有可能存在副作用,也因此,函数的调用是有先后依赖性的,就徒增很多麻烦。另一方面,我认为Monad更像是命令式编程中的类。大家都知道C++是面向对象的C语言,可是从来没有任何事情是C++能做到而C做不到的。那我们为什么还要面向对象而不继续写过程呢?其实我也见过面向对象的抨击者说,面向对象无非是把原先的全局变量放到类里面而已,其实仍旧可以在函数间隐性地共享。这并没有说错,可我们仍不能否认面向对象的优点:它给我们一个用以组织代码关系的结构。有了类,我们可以根据抽象的功能来把原先的变量、函数以更高的层次归类。同样的,我们为什么要Monad呢?虽然也可以从编译器层面实现副作用,但Monad给了我们根据功能来组织代码的工具:实现随机数可以用Random Monad,IO可以用IO Monad,修改状态可以用State Monad,等等,这远远好过用一个全局变量保存随机数种子,又用一个全局变量保存某某状态这样松散的组织结构。