# LISP的永恒之道

2017/09/25 13:31

#### Lisp之源

Lisp意为表处理(List Processing)，源自设计者John McCarthy于1960年发表的一篇论文《符号表达式的递归函数及其机器计算》。McCarthy在这篇论文中向我们展示了用一种简单的数据结构S表达式(S-expression)来表示代码和数据，并在此基础上构建一种完整的语言。Lisp语言形式简单、内涵深刻，Paul Graham在《Lisp之根源》中将其对编程的贡献与欧几里德对几何的贡献相提并论。

#### Lisp之形

foo

()

(a b (c d) e)

(+ (* 2 3) 5)

(defun factorial (N)
(if (= N 1)
1
(* N (factorial (- N 1)))
)
)

#### Lisp之道

<func name='gcd' return_type='int'>
<params>
<a type='int'/>
<b type='int'/>
</params>
<body>
<if>
<equals>
<a/>
<int>0</int>
</equals>
</if>
<then>
<return><b/></return>
</then>
<else>
<return>
<gcd>
<modulo><b/><a/></modulo>
<a/>
</gcd>
</return>
</else>
</body>
</func>

<class name="Computer">
<field access="private" type="MainBoard" name="main-board" />
<field access="private" type="CPU" name="cpu" />
<field access="private" type="Memory" name="memory" />

<method access="public" return_type="boolean" name="powerOn" />
<params>...</params>
<body>...</body>
</method>

<method access="public" return_type="boolean" name="powerOff" />
<params>...</params>
<body>...</body>
</method>
</class>

<sql>
<select>
<column name="employees.id" />
<column name="bonus.amount" />
</select>
<from>
<table name="employees" />
<table name="bonus" />
</from>
<where>
<equals>
<column name="employees.id" />
<column name="bonus.employee_id" />
</equals>
</where>
</sql>

<project name="MyProject" default="dist" basedir=".">
<property name="src" location="src"/>
<property name="build" location="build"/>
<property name="dist"  location="dist"/>

<target name="init">
<mkdir dir="${build}"/> </target> <target name="compile" depends="init" description="compile the source " > <javac srcdir="${src}" destdir="${build}"/> </target> <target name="dist" depends="compile" description="generate the distribution" > <mkdir dir="${dist}/lib"/>
<jar jarfile="${dist}/lib/MyProject-${DSTAMP}.jar" basedir="${build}"/> </target> <target name="clean" description="clean up" > <delete dir="${build}"/>
<delete dir="\${dist}"/>
</target>
</project>

Lisp的柔性与S表达式有着密切的关系。Lisp并不限制你用S表达式来表达什么语义，同样的S表达式语法可以表达各种不同领域的语义，这就是语法和语义解耦。如果说普通语言的刚性源于“语法和语义紧耦合”，那么Lisp的柔性正是源于“语法和语义解耦”！“语法和语义解耦”使得Lisp可以随意地构造各种领域的DSL，而不强制用某一种范式或是领域视角去分析和解决问题。本质上，Lisp编程是一种超越了普通编程范式的范式，这就是Lisp之道：面向语言编程(LOP, Language Oriented Programming)。Wikipedia上是这样描述LOP的：

Language oriented programming (LOP) is a style of computer programming in which, rather than solving problems in general-purpose programming languages, the programmer creates one or more domain-specific languages for the problem first, and solves the problem in those languages … The concept of Language Oriented Programming takes the approach to capture requirements in the user’s terms, and then to try to create an implementation language as isomorphic as possible to the user’s descriptions, so that the mapping between requirements and implementation is as direct as possible.

LOP范式的基本思想是从问题出发，先创建一门描述领域模型的DSL，再用DSL去解决问题，它具有高度的声明性和抽象性。SQL、make file、CSS等DSL都可以被认为是LOP的具体实例，下面我们再通过两个常见的例子来理解LOP的优势。

#### Lisp之器

Lisp拥有强大的自解释特性，这得益于独一无二的Lisp之器：宏 (macro)。宏使得Lisp编写的DSL可以被Lisp解释器直接解释执行，这在原理上与内部DSL是相通的，只是内部DSL一般是利用宿主语言的链式调用等特性，通常形式简陋，功能有限，而Lisp的宏则要强大和灵活得多。

C语言中也有宏的概念，不过Lisp的宏与C语言的宏完全不同，C语言的宏是简单的字符串替换。比如，下面的宏定义：

#define square(x) (x*x)

square(1+1)的期望结果是4，而实际上它会被替换成(1+1*1+1)，结果是3。这个例子说明，C语言的宏只在预编译阶段进行简单的字符串替换，对程序语法结构缺乏理解，非常脆弱。Lisp的宏不是简单的字符串替换，而是一套完整的代码生成系统，它是在语法解析的基础上把Lisp代码从一种形式转换为另一种形式，本质上起到了普通语言编译器的作用。不同的是，普通编译器是把一种语言的代码转换为另一种语言的代码，比如，Java编译器把Java代码转换成Java字节码；而Lisp宏的输入和输出都是S表达式，它本质上是把一种DSL转换为另一种DSL。下面的例子是宏的一个典型用法。

#### 总结

Lisp采用单一的S表达式语法表达不同的语义，实现了语法和语义解耦。这使得Lisp具有强大的语义构造能力，擅长于构造DSL实现面向语言编程，而宏使得Lisp具有自解释能力，让不同DSL之间的转换游刃有余。进入Lisp的世界应当从理解面向语言编程入门，这是Lisp之道，而函数式编程和宏皆为Lisp之器，以道驭器方为正途。

#### 参考

The Roots of Lisp

The Nature of Lisp

Why Lisp macros are cool, a Perl perspective

Wikipedia: Language-oriented programming

《实用Common Lisp编程》

《冒号课堂 – 编程范式与OOP思想》

0
0 收藏

0 评论
0 收藏
0