一到周末,Hello World 咖啡馆就比平时热闹得多, 各种语言都来到这里,互相打探对方的最新特性,看看自己能不能借鉴一些。
这天晚上,由于Lisp的到来,咖啡馆的气氛显得格外热烈。
Lisp身穿一身时髦又奇异的括号服装, 和Clojure, Scala等几个函数式编程的忠实拥趸坐在一桌,谈笑风声,时不时挖苦一下隔壁的几个人,那里坐着以C语言为首的几个大佬。
他悠闲地端起了一杯咖啡,悠悠地说道:“听说Java也用上了函数式编程? ”
Clojure道:“是啊,加上去没多久,好多人还没用熟呢!”
Lisp不屑地说道:“加上了也没用,不是我瞧不起他们,表达能力实在是太弱了!”
隔壁的C语言早就憋了一肚子火, 听到这句话,忍不住说道:“你嘚瑟什么呀,我知道你是基于Lambda演算的,但我们是基于图灵机的啊, 上世纪已经证明这图灵机和Lambda演算是等价的,所以我们的能力是一样的。 对了,你知不知道什么是图灵机,以及他的实现冯诺依曼架构啊?”
Lisp懒懒地说:“没兴趣了解,理论上计算能力是等价的,但并不代表在语言层面的表示一样,比如说,在我这里非常自然的函数式编程,在你们那里看起来就很别扭,是不是啊Java老弟?”
Java有点不好意思,他很清楚,自己的函数式编程就是一个半吊子,所谓的Lambda表达式就是个接口实现而已。
(详情参见:《Lambda表达式有什么用?》)
Python出来解围:“ 我这里支持得很好啊, 我可以把函数当做参数进行传递,可以把函数当做返回值返回,还可以把函数保存到数据结构中!高阶函数map, reduce用起来也贼爽。”
JavaScript接口道:“我也是啊!”
Lisp 笑了一下,接着问道:“你们能在运行时创建新的函数吗?”
Java 看到展示自己的机会来了,马上接口道:“怎么不能? 在程序运行的时候,我可以通过操作Java字节码的方法,动态地生成函数和类,人们经常说的AOP就是这么玩的。”
Lisp很不屑:“还操纵字节码,还AOP,麻烦不麻烦! 低级不低级!丢人不丢人! ”
Java一脸愕然。
Lisp开始放大招: “你们能不能写个函数,把代码传递进去,然后对代码进行修改,然后返回新的代码?”
一个修改代码的函数? 代码可以在运行时修改? 这不是自虐吗?
“哈哈,这你们就不懂了吧, 凭什么数据结构可以变化,并且其中的数据可以修改,而程序却不能呢?在我这里,代码就是数据,代码可以在运行时被修改。” Lisp很得意。
JavaScript愤愤地说:“这有什么鸟用?”
Lisp看了看身边的Java ,又拿他开刀:“就拿你来举例吧, 你最早只有for 循环,没有for each 循环, 就是下面这样, 是不是被很多人骂啊?”
普通for 循环
for(int i=0;i<persons.size();i++){
Person p = persons.get(i);
System.out.println(p);
}
for each 循环
for (Person p : perons){
System.out.println(p);
}
Java无奈地点点头,Lisp说的是实话。
“这就是了,要想把这个新的语法给加上,那必须得在语言层面修改才行,对吧? 语言不支持,程序员骂也没用。 但是在我Lisp里就不一样了,根本就不用改动语言,一个程序员就能把这个新的语法给加上,并且新的for each 和 老的for 处于同等地位,相当于语言被扩展了。 ”
“不会吧,你怎么实现?”
“自然是使用宏(Macro)了!”
“什么是宏? 和C老大的宏一样吗?” Java问道。
“C语言的宏就是编译期的文本替换而已,怎么能和我的宏相比? 给你说你也听不懂,通俗点说宏也是程序员写的代码, 运行时可以把代码传递给宏,然后宏可以修改这段代码,返回新的代码。 拿刚才的例子来说,假设for是个函数,程序员可以写一个for-each 的宏,在这个宏中,修改/扩展for函数的代码,实现for each的功能。”
C语言若有所思:“嗯,看来在你这里,程序也是数据啊,可以任意操作,果然厉害。如果把一段程序看成是抽象语法树(AST),那这个宏就相当于把AST做了修改,形成了新的AST。”
Lisp心想,这老家伙还不赖, 已经Get到了。
他说:“你们老是笑话我的括号多,但是你们不明白的是,这恰恰是我最大的优势,因为在我这里,数据和程序表示的方式是一致的,都是list,比如这一段简单的循环。”
(loop for x in '(1 2 3 4 5)
do (print x) )
大家一看,果然如此,这代码(loop函数)是用括号括起来的一个List, 这数据(1 2 3 4 5)也是一个List。
Lisp说:“List的好处就是天然可以和AST对应,这就方便宏来操作了。”
JavaScript说:“我还是看不出有什么用?”
Lisp接着说:“正是由于程序可以当做数据来处理,程序员可以无限地扩充Lisp,把Lisp变成他们所在领域的语言,使用这个领域特定的语言来编程,那简直是如虎添翼,例如这个例子,通过扩展Lisp, Lisp和SQL语言完美地融合了。 ”
(select [age] :from [employee] :where [> [salary] 50000])
“再比如说, 在座的各位大都支持面向对象编程,对我来说,在我这里实现OOP也是小菜一碟,比如Common Lisp中的CLOS,就是用宏实现的OOP操作集。”
大家都表示很好奇, Lisp就展示了几行代码:
定义一个叫Person的类
(defclass person ()
((name :accessor person-name
:initarg :name)
(age :accessor person-age
:initarg :age)))
创建一个person对象
(setf p (make-instance 'person :name "andy" :age 10))
访问对象的属性
(print (person-name p))
看到这里,大家都自愧弗如了, 这Lisp真有狂的资本啊。
C 语言说到:“看来你Lisp是用来开发其他语言的语言啊!”
Lisp笑道:“这句话不错,在我这里,语言和程序之间的界限是非常模糊的。”
C语言问了最后一个问题:“既然你这么牛? 为什么用的人这么少?”
Lisp站起身来,叹了一口气:“有人说学起来门槛太高;有人说灵活与强大是一把双刃剑,每个人都能造属于自己的领域特定语言,让别人难以理解和维护;还有人说Lisp方言很多,社区分裂,没有统一的库让新手学习使用。 谁知道呢?”
说完Lisp便离开了,只留下一个满是括号的背影。