查看原文
其他

为什么“无人问津”的Lisp可以这么狂?

码农翻身刘欣 码农翻身 2021-04-20


一到周末,Hello World 咖啡馆就比平时热闹得多, 各种语言都来到这里,互相打探对方的最新特性,看看自己能不能借鉴一些。 


这天晚上,由于Lisp的到来,咖啡馆的气氛显得格外热烈。 


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便离开了,只留下一个满是括号的背影。


最新热门文章

为什么学习编程要从Web开始?

我是一个线程

我是一个Java Class

CPU阿甘

面向对象圣经

TCP/IP之大明邮差

负载均衡的原理

一个故事讲完HTTPs

编程语言的巅峰

JavaScript:一个屌丝的逆袭

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存