Haskell中概念的理解(一)

刚才看了Bryan O’Sullivan的一个演讲《Running a startup on Haskell》,讲公司招人以及使用Haskell的一些现状。很赞同Brayan说的一句话“if cerntain skills are really widely distributed in a population they become completely meaningless”,如果某个技术非常流行,说懂这个东西毫无意义,如果一个人在简历里写他懂PHP懂Java,就像是在告诉别人他长了个脑袋。

断断续续学习Haskell也好几年了,没事的时候就会拿它作为一种思维训练,试图准确理解其中的概念,但是智力实在不够,每次多理解一些而已。最近好像突然领悟了很多(不敢说“想通”),不知是不是量变到质变的一个过程。

最近连续读了很多材料,有些是以前读过但未理解的。回想这个过程,应该是从理解Polymorphic Typechecking开始,读的是Basic Polymorphic Typechecking,主要收获是了解了大致的算法,对datatype(type constructor)有个更具体的感知。学习Haskell,理解类型多态很重要,只有理解了它才能理解typeclass和高阶多态,其实类型多态很像C++的模板,只是用在type constructor的定义中让人感到理解困难而已,尤其是type constructor中包含函数时,考虑下datatype中类型变量的顺序问题会有助于理解这个问题。然后重读了《Learn You a Haskell for Great Good!》第八节“Making Our Own Types and Typeclasses”,这一节算起来读过不止一次,但仅这次完全读懂,对类型理解也更加深刻,尤其是我感到自己可以很容易的理解StateMonad那诡异的s->(a,s)了,于是开始读一些Monad的tutorial

这几年学Haskell,大部分时间都花在Monad上,很早就会用,但是理解不到这个东西的好处,以及Monad到底如何“隔离副作用”,更别提自己在何时、如何设计一个Monad了。最近这块感悟比较多,有几个重要材料值得参考。Haskell两本免费教材《Learn You a Haskell for Great Good!》和《Real World Haskell》中Monad相关章节很重要。《All About Monads》对每个常用的Monad都有讲解,文中第一段直指Monad核心,理解这句话就理解了Monad,但是理解这句话是结果,试图理解这句话本身并没有太大意义;文中提到Monad的三个重要属性Modularity、Flexibility、Isolation ,其实这三个属性说的是一回事,理解这点很重要,同样理解这点是结果,另外要理解One-Way-Monad以及为什么Maybe不是One-Way-Monad,这是隔离副作用的关键。然后是Brian Beckman的视频“Don’t fear the Monad”,这个视频我很早以前看的时候还以为他讲跑题了,这次看完才恍然大悟,Brian的Bind类型推导过程也很精彩,看的时候回想一下《All About Monads》的第一段。正如Brian也提到的,其实Monad的所有魔力都在于那个bind(>>=)函数,每个Monad有自己个性化的bind。对于One-Way-Monad,这个函数使得我们可以操作m a中的a,而不会将a泄露出去,这是Monad的隔离性。一组相同的Monad(如IO)可以放到一个do-notation里执行,也就是combining computations,这种组合方式其实是一种模块化,它将一类操作关联到一处,而不影响其他部分,举例说,如果出现大量的嵌套case+Maybe语句,就可以将这些Maybe放到一个do-notation里,精简代码而又永远不会出错,因为与Maybe有关的计算都在bind里定义了。这样一个do-notation其实代表了某一类型Monad的一个计算序列(bind),而其计算的内容在其内部(类型m a的a)无法被外界直接取得,除非通过bind,也就是《All About Monads》的第一句话,“A monad is a way to structure computations in terms of values and sequences of computations using those values. ”

理解Monad之后才发现,其实这个概念并不难,理解它也并不一定需要范畴论的知识。让我吃惊的是发现在haskell中,这东西并不是什么重要的组成部分,举个不恰当的例子,就好像微积分在量子物理学中的作用一样,这是个必备技能,但根本没有触到关键。学习Haskell的难点还在于类型上,从类型的角度思考问题,通过类型来做问题抽象,才是困难之处。

下面说说typeclass,这是haskell中的一个核心概念,从SPJ的一个分享来看,这个东西最初只是一个实验功能。可以从Mark P. Jones的paper《Functional Programming with Overloading and Higher-Order Polymorphism》中找到这个概念诞生的原因。typeclass是为了解决任意多态类型所存在问题的一个折中方案。考虑函数size(例子瞎举的),它返回“某个数据结构的的大小”,如果希望它是多态的,而不是每次都要定义某个类型自己的sizeA::A->Int,sizeB::B->Int的话,就需要定义size::a->Int,但是如果不对a的类型加以限制,就可以将没有size的类型应用于这个函数,导致运行时错误,这是haskell这类静态类型语言所无法容忍的。于是typeclass出场了,通过定义一个typeclass叫做Countable,并将size定义为size::Countable a => a -> Int,对于每个应用于size的类型,都要求其为Countable的instance,否则类型检查失败,这样就非常优雅的解决了这个问题。可以看出typeclass更加接近interface。总之,它最初的出现是为了让haskell更精确的做类型推导,不会出现运行时错误,同事保持多态的优美。我前面说它是个折中方案是因为,(我认为)它其实是一种人工向编译器提供类型信息的方法,同时增加了不小的编码成本。

Mark P. Jones的paper非常优秀,其中不仅讲解了typeclass,还介绍了kind system,这是理解haskell类型系统的一个重要部分。有时间要细读读Mark以及Simon Peter Jones, Simon Marlow, John Hughes, Philip Wadler这群神人的paper,正是由于Haskell背后有这群人,这门语言才会这样魅力四射、引人入胜。

Haskell中还有很多其他内容没有花时间去学习理解,比如并发和Arrow,估计近期也没太多时间了。