『阿男的编程本质论』*11 Eval,Macro,Preprocessor,Homoiconicity(四)*
最后我们说说Haskell。Haskell并不是Homoiconic的语言,而且Haskell也不需要Macro。为什么呢?因为Haskell有自己非常完善的类型系统的设计,而且Haskell的function都是Lazy-Evaluation的。
我们使用Clojure的macro,也等于是Lazy-Evaluation,因为我们传入macro的参数即便是代码,实际上也是做为list数据来对待,而parser不会马上做语法分析。只有macro完全展开以后,才会做语法分析。因此macro的展开这一步是本身是一次parsing,展开后的代码再进行一次parsing,这两次的parsing是一样的,并没有什么不同,这和C语言那种macro进行文本替换的方式是不同的。因此我们的macro在展开时的错误就是代码本身的错误,这就让错误信息很准确,分析错误也就变得更容易。
Haskell则是采取了完全不同的设计使得Haskell里面并不需要macro。为什么这么说呢?因为首先Haskell的function全部是lazy evaluated的,其次Haskell的function是可以被当作参数来传递的。有了这两点,Haskell语言本身即可以实现lisp macro所起到的"模版"的功能,因为我们可以通过定义各种函数来实现很多抽象的概念。比如下面这段Haskell代码^1:
Prelude> doif x y = if x then (Just y) else Nothing
上面的代码定义了一个函数叫做doif
,这个函数接受两个参数x
和y
,其中x
是判断条件,而y
是根据条件可能的结果。Just
和Nothing
都是Maybe
类型的monad,你如果不回Haskell语言可以先不管含义。我们看看doif
函数的类型定义:
Prelude> :t doif
doif :: Bool -> a -> Maybe a
可以看到doif
接受的第一个参数x
是Bool
类型的数据,这个是Haskell通过if
判断出来的。if
需要一个Bool
类型的参数做为判断。然后第二个参数的类型是a
,是个类型参数,也就是说y
可以是任何东西,可以是数据,也可以是函数,等等。
最后,返回结果是Maybe a
,就是把a
封装在Maybe
里面。你不需要知道Maybe
是什么,但理解它是个盒子的种类就行了。这个盒子里可以是空的,用Nothing
代表,也可以装着东西,用Just ...
代表,我们这里参数是y
,因此可能装的就是Just y
。我们用用看这个doif
:
Prelude> doif (1 == 2) "Yes 1 == 2 :-)"
Nothing
我们使用doif
判断x
,也就是1==2
是否成立,如果成立,那么我们的y
,也就是Yes 1 == 2 :-)
将被装进Just
盒子里面返回。注意Haskell允许我们把(1 == 2)
这个表达式做为参数传递,因为这个表达式的执行结果会返回True或者False。
我们再看doif
判断为True的使用:
Prelude> doif (1 == 1) "Yes 1 == 1"
Just "Yes 1 == 1"
注意这是阿男要说的Haskell的第一个特点:表达式,函数都可以作为参数传递,这叫做First-Class Function
。
第二个特点:作为参数的表达式或者函数不会被执行后传入函数,而是传入后执行。也就是说,因为我们调用doif
,导致了doif
需要第一个参数,导致了第一个参数被解析。如果参数是表达式或者函数,此时表达式才被解析和运行,这叫做Lazy Evaluation
。
因此Haskell本身的设计免去了macro的需求,而是用First-Class Function
和Lazy Evaluation
使得我们可以定义所需功能。