本节书摘来自异步社区《Clojure编程乐趣》一书中的第1章,第1.1节1.2 为何(又一种)Lisp,作者 【美】Michael Fogus , Chris Houser,更多章节内容可以访问云栖社区“异步社区”公众号查看
1.2 为何(又一种)Lisp
Clojure编程乐趣
一套好的概念可以将大脑从无用功中解放出来,专注于更高级的问题。
—Alfred North Whitehead
去到任何一个开源项目托管站点,搜索“Lisp interpreter”(Lisp解析器)。这个貌似平淡无奇的搜索,可能会带给我们一个堆积如山1**的结果。事实上,计算机科学的历史中堆积着大量废弃的Lisp实现(Fogus 2009)。诸多初衷良好的Lisp来了又走,一路遭到无数嘲笑,但到了明天,搜索结果依然会无限制增长。既然有如此惨痛的过往,为何还有人愿意将其崭新的程序设计语言构建于Lisp模型之上呢?
1.2.1 优美
Lisp吸引了计算机科学史上最聪明的一群头脑。但是,仅有权威的争论是不够的,我们不该仅凭此判断Lisp。只有用其编写应用,才可以直接看得到Lisp语言家族的真正价值。Lisp的风格就在于极具表现力、非常实用,以及在大多数情况下表现出的美感。最初的Lisp语言是John McCarthy在其惊天动地的论文“Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I”(McCarthy 1960)中定义的,只用区区7个函数和两个特殊form便定义出整个语言:atom、car、cdr、cond、cons、eq、quote、lambda和label。
通过这9个form的组合,McCarthy将整个计算以一种令人窒息的方式呈现出来。计算机程序员总在寻找美,而多数情况下,美会以简单的形式自我呈现出来。7个函数2个特殊form,美无过于此。
1.2.2 极度灵活
Lisp何以历经五十余年而弥新,相较之下,无数语言却成了匆匆过客?个中原因可能极尽复杂,但究其根因,无外乎Lisp自身的语言基因(Tarver 2008)将语言的灵活性推向极致。Lisp新手常常气馁,无处不在的括号和前缀记法,与非Lisp程序设计语言大相径庭。然而,正是这种行为上的规律性,不仅让需要记忆的语法规则减少了,也让宏的编写变得很简单。我们会在第8章更详细地了解宏,但为了让你开开胃,这里先简单地看一下。下面是一个例子,稍后再来细究:
![652f418fc4db8d8e4b219bd6eb4e72f290ead9a6](https://i-blog.csdnimg.cn/blog_migrate/78ad4f32434ce04e6d54946d59e13e56.png)
希望你对这些单词有所了解,因为这不是一本SQL的书。这里要说的是,Clojure并没有内建对SQL的支持。SELECT、FROM等这些词并不是内建的form。它们也不是常规的函数,如果SELECT是,那么使用a、b和c就错了,因为它们还没有定义。
如何用Clojure定义这样的领域特定语言(domain-specific language,DSL)呢?好吧,这不是产品就绪(production-ready)的代码,没有绑定到真实的数据库服务器上;但只要有了程序1.1列出的一个宏和三个函数,前面的查询就能够返回下面这些实际的值:
![e7908793e2a1caed7810f706b77ece039ef21ea3](https://i-blog.csdnimg.cn/blog_migrate/c50d4befe58cdb1d66eb9ae03da7a764.png)
请注意,FROM和ON这样的词是从输入表达式中直接取出来的,而其他诸如~max和AND则要特殊对待。调用查询时,max得到一个5,这是从字面量SQL字符串中提取的,由一个单独向量提供,以这种方式准备的查询颇为完美,可以免受SQL注入攻击。AND form由Clojure的前缀表达式转成SQL所需的中缀表达式。
程序1.1 以Clojure编写领域特定语言,用以嵌入SQL查询
![461811e57598376e94ea3ae5c326e615363a6a1d](https://i-blog.csdnimg.cn/blog_migrate/d681bd6a3ce4b4cad7456a1d87c594aa.png)
但需要指出的是,这算不上是一种很好的SQL DSL—还有实现更为完整的。2我们要说的是,一旦懂得了这种创建DSL的技巧,就可以识别出一些机会,定义自己的DSL,解决比SQL更窄的、更加面向应用的问题。无论是给不常见的非SQL数据库提供查询语言,还是给模糊的数学学科提供一种方式表现函数,抑或是处理其他自己都未曾想过的应用,能够拥有如此灵活易扩展的基础语言,且不损伤语言自身特性,都将成为游戏规则的改变者。
虽然我们不该太过深入细节地讨论实现,但还是要顺着之前讨论过的一些重要方面,简单看看列表1.1的实现。
自下而上阅读,首先映入眼帘的是入口点,SELECT宏。它返回的是一个有两项的vector—第一项通过调用 expand-clause生成,返回的是一个经过转换的查询字符串,而第二项是另一个vector,表示输入里由~标记的表达式。~表示反quote,我们会在第8章讨论其更常见的用法。另外要注意的是这里用到的tree-seq,通过它可以很容易地将感兴趣的项从值树(也就是输入表达式)上提取出来。
expand-clause函数用语句的第一个词,在clause-map里进行了查询,然后,调用适当的函数,完成从Clojure的s表达式(s-expression)到SQL字符串的转换。clause-map为SQL表达式各个部分提供了所需的详细功能:插入逗号或是其他SQL语法,有时还要递归调用expand-clause进行子语句的转换。其中之一是WHERE语句,通过委托给expand-expr函数,处理了SQL所需的前缀表达式到中缀表达式的通用转换。
总的来说,这个例子展示的Clojure灵活性大多是因为宏可以接受代码form(比如前面展示的这个SQL DSL的例子),并将其当做数据对待—遍历树、转换值等。之所以可以这样做,不只是因为代码可以当做数据,还因为在Clojure程序里,代码就是数据。
1.2.3 代码即数据
“代码即数据”这样的说法最初很难理解。实现一门程序设计语言,代码同数据一般对待,这需要语言本身具有非常强的可塑性。当语言就是以这种本质的数据结构表现时,语言本身就可以操作自己的结构和行为了(Graham 1995)。读到上面这句话,我们脑海中可能会浮现出一条衔尾蛇(Ouroboros)3,也许这么说不合适,因为Lisp可以比作一个自我舔食的棒棒糖—更正规的说法是同像性(homoiconicity)。要完全掌握Lisp的同像性,需要跨越一个巨大的概念鸿沟,但在本书里,我们尽力帮你理解这个概念,希望你最终能够领会其巨大的威力。
初学Lisp是一番乐趣,如果你能从本书得到同样的体验,那么我们欢迎你—甚至有点嫉妒。
1……且疯狂的。
2要提到的一个是ClojureQL,它位于http://gitorious.org/clojureql。
3译注:衔尾蛇(Ouroboros)是自古流传至今的一种符号,大致形象是一条蛇正吞食自己的尾巴,结果形成了一个圆环。