为什么我喜欢 Lisp 语言
fmms 13年前
<p>本文是从 <a href="/misc/goto?guid=4958193227261902577" target="_blank">Why I love Lisp</a> 这篇文章翻译而来。</p> <hr /> <blockquote> <p>这篇文章是我在<a href="/misc/goto?guid=4958193227999587131" target="_blank">Simplificator</a>——我工作的地方——的一次座谈内容的摘录,座谈的题目叫做“为什么我喜欢Smalltalk语言和Lisp语言”。在此之前,我曾发布过一篇叫做“ <a href="/misc/goto?guid=4958190603986173430" target="_blank">为什么我喜欢Smalltalk?</a>”的文章。</p> </blockquote> <div style="width:310px;"> <p>大漠黄沙 by Guilherme Jófili</p> </div> <p>Lisp是一种很老的语言。非常的老。Lisp有很多变种,但如今已没有一种语言叫Lisp的了。事实上,有多少Lisp程序员,就有多少种Lisp。这是因为,只有当你独自一人深入荒漠,用树枝在黄沙上为自己喜欢的Lisp方言写解释器时,你才成为一名真正的<em>Lisp程序员</em>。</p> <p>目前主要有两种Lisp语言分支:<a href="/misc/goto?guid=4958193229416944592" target="_blank">Common Lisp</a> 和 <a href="/misc/goto?guid=4958193230160972074" target="_blank">Scheme</a>, 每一种都有无数种的语言实现。各种Common Lisp实现都大同小异,而各种Scheme实现表现各异,有些看起来非常的不同,但它们的基本规则都相同。这两种语言都非常有趣,但我却没有在实际工作 中用过其中的任何一种。这两种语言中分别在不同的方面让我苦恼,在所有的Lisp方言中,我最喜欢的是<a href="/misc/goto?guid=4958183920711387805" target="_blank">Clojure语言</a>。我不想在这个问题上做更多的讨论,这是个人喜好,说起来很麻烦。<br /> <br /> Clojure,就像其它种的Lisp语言一样,有一个REPL(Read Eval Print Loop)环境,你可以在里面写代码,而且能马上得到运行结果。例如:</p> <div> <div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>1</code></td> <td><code>5</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>2</code></td> <td><code>;=> 5</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>3</code></td> <td><br /> </td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>4</code></td> <td><code>"Hello world"</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>5</code></td> <td><code>;=> </code><code>"Hello world"</code></td> </tr> </tbody> </table> </div> </div> </div> <p>通常,你会看到一个提示符,就像<code>user></code>,但在本文中,我使用的是更实用的显示风格。这篇文章中的任何REPL代码你都可以直接拷贝到<a href="/misc/goto?guid=4958193231575853153" target="_blank">Try Clojure</a>运行。</p> <p>我们可以像这样调用一个函数:</p> <div> <div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>1</code></td> <td><code>(</code><code>println</code> <code>"Hello World"</code><code>)</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>2</code></td> <td><code>; Hello World</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>3</code></td> <td><code>;=> nil</code></td> </tr> </tbody> </table> </div> </div> </div> <p>程序打印出“Hello World”,并返回<code>nil</code>。我知道,这里的括弧看起来好像放错了地方,但这是有原因的,你会发现,他跟Java风格的代码没有多少不同:</p> <div> <div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>1</code></td> <td><code>println</code><code>(</code><code>"Hello World"</code><code>)</code></td> </tr> </tbody> </table> </div> </div> </div> <p>这种Clojure在执行任何操作时都要用到括弧:</p> <div> <div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>1</code></td> <td><code>(+ 1 2)</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>2</code></td> <td><code>;=> 3</code></td> </tr> </tbody> </table> </div> </div> </div> <p>在Clojure中,我们同样能使用向量(vector):</p> <div> <div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>1</code></td> <td><code>[</code><code>1 2 3 4</code><code>]</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>2</code></td> <td><code>;=> </code><code>[</code><code>1 2 3 4</code><code>]</code></td> </tr> </tbody> </table> </div> </div> </div> <p>还有符号(symbol):</p> <div> <div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>1</code></td> <td><code>'symbol</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>2</code></td> <td><code>;=> symbol</code></td> </tr> </tbody> </table> </div> </div> </div> <p>这里要用引号('),因为Symbol跟变量一样,如果不用引号前缀,Clojure会把它变成它的值。list数据类型也一样:</p> <div> <div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>1</code></td> <td><code>'(li st)</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>2</code></td> <td><code>;=> (li st)</code></td> </tr> </tbody> </table> </div> </div> </div> <p>以及嵌套的list:</p> <div> <div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>1</code></td> <td><code>'(l (i s) t)</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>2</code></td> <td><code>;=> (l (i s) t)</code></td> </tr> </tbody> </table> </div> </div> </div> <p>定义变量和使用变量的方法像这样:</p> <div> <div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>1</code></td> <td><code>(</code><code>def</code> <code>hello-world </code><code>"Hello world"</code><code>)</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>2</code></td> <td><code>;=> #</code><code>'user</code><code>/hello-world</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>3</code></td> <td><br /> </td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>4</code></td> <td><code>hello-world</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>5</code></td> <td><code>;=> </code><code>"Hello world"</code></td> </tr> </tbody> </table> </div> </div> </div> <p>我的讲解会很快,很多细节问题都会忽略掉,有些我讲的东西可能完全是错误的。请原谅,我尽力做到最好。</p> <p>在Clojure中,创建函数的方法是这样:</p> <div> <div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>1</code></td> <td><code>(</code><code>fn</code> <code>[</code><code>n</code><code>]</code> <code>(* n 2))</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>2</code></td> <td><code>;=> #<user$eval1$fn__2 user$eval1$fn__2@175bc6c8></code></td> </tr> </tbody> </table> </div> </div> </div> <p>这显示的又长又难看的东西是被编译后的函数被打印出的样子。不要担心,你不会经常看到它们。这是个函数,使用<code>fn</code>操作符创建,有一个参数<code>n</code>。这个参数和2相乘,并当作结果返回。Clojure和其它所有的Lisp语言一样,函数的最后一个表达式产生的值会被当作返回值返回。</p> <p>如果你查看一个函数如何被调用:</p> <div> <div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>1</code></td> <td><code>(</code><code>println</code> <code>"Hello World"</code><code>)</code></td> </tr> </tbody> </table> </div> </div> </div> <p>你会发现它的形式是,括弧,函数,参数,反括弧。或者用另一种方式描述,这是一个列表序列,序列的第一位是操作符,其余的都是参数。</p> <p>让我们来调用这个函数:</p> <div> <div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>1</code></td> <td><code>((</code><code>fn</code> <code>[</code><code>n</code><code>]</code> <code>(* n 2)) 10)</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>2</code></td> <td><code>;=> 20</code></td> </tr> </tbody> </table> </div> </div> </div> <p>我在这里所做的是定义了一个匿名函数,并立即应用它。让我们来给这个函数起个名字:</p> <div> <div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>1</code></td> <td><code>(</code><code>def</code> <code>twice (</code><code>fn</code> <code>[</code><code>n</code><code>]</code> <code>(* n 2)))</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>2</code></td> <td><code>;=> #</code><code>'user</code><code>/twice</code></td> </tr> </tbody> </table> </div> </div> </div> <p>现在我们通过这个名字来使用它:</p> <div> <div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>1</code></td> <td><code>(twice 32)</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>2</code></td> <td><code>;=> 64</code></td> </tr> </tbody> </table> </div> </div> </div> <p>正像你看到的,函数就像其它数据一样被存放到了<em>变量</em>里。因为有些操作会反复使用,我们可以使用简化写法:</p> <div> <div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>1</code></td> <td><code>(</code><code>defn</code> <code>twice </code><code>[</code><code>n</code><code>]</code> <code>(* 2 n))</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>2</code></td> <td><code>;=> #</code><code>'user</code><code>/twice</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>3</code></td> <td><br /> </td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>4</code></td> <td><code>(twice 32)</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>5</code></td> <td><code>;=> 64</code></td> </tr> </tbody> </table> </div> </div> </div> <p>我们使用if来给这个函数设定一个最大值:</p> <div> <div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>1</code></td> <td><code>(</code><code>defn</code> <code>twice </code><code>[</code><code>n</code><code>]</code> <code>(</code><code>if</code> <code>(> n 50) 100 (* n 2))))</code></td> </tr> </tbody> </table> </div> </div> </div> <p>if操作符有三个参数:断言,当断言是true时将要执行的语句,当断言是 false 时将要执行的语句。也许写成这样更容易理解:</p> <div> <div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>1</code></td> <td><code>(</code><code>defn</code> <code>twice </code><code>[</code><code>n</code><code>]</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>2</code></td> <td><code> </code><code>(</code><code>if</code> <code>(> n 50)</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>3</code></td> <td><code> </code><code>100</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>4</code></td> <td><code> </code><code>(* n 2)))</code></td> </tr> </tbody> </table> </div> </div> </div> <p>非常基础的东西。让我们来看一下更有趣的东西。</p> <p>假设说你想把Lisp语句反着写。把操作符放到最后,像这样:</p> <div> <div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>1</code></td> <td><code>(4 5 +)</code></td> </tr> </tbody> </table> </div> </div> </div> <p>我们且把这种语言叫做Psil(反着写的Lisp...我很聪明吧)。很显然,如果你试图执行这条语句,它会报错:</p> <div> <div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>1</code></td> <td><code>(4 5 +)</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>2</code></td> <td><code>;=> java.lang.ClassCastException: java.lang.Integer cannot be cast to clojure.lang.IFn (NO_SOURCE_FILE:0)</code></td> </tr> </tbody> </table> </div> </div> </div> <p>Clojure会告诉你<code>4</code>不是一个函数(函数是必须是<code>clojure.lang.IFn</code>接口的实现)。</p> <p>我们可以写一个简单的函数把Psil转变成Lisp:</p> <div> <div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>1</code></td> <td><code>(</code><code>defn</code> <code>psil </code><code>[</code><code>exp</code><code>]</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>2</code></td> <td><code> </code><code>(reverse exp))</code></td> </tr> </tbody> </table> </div> </div> </div> <p>当我执行它时出现了问题:</p> <div> <div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>1</code></td> <td><code>(psil (4 5 +))</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>2</code></td> <td><code>;=> java.lang.ClassCastException: java.lang.Integer cannot be cast to clojure.lang.IFn (NO_SOURCE_FILE:0)</code></td> </tr> </tbody> </table> </div> </div> </div> <p>很明显,我弄错了一个地方,因为在psil被调用之前,Clojure会先去执行它的参数,也就是<code>(4 5 +)</code>,于是报错了。我们可以显式的把这个参数转化成list,像这样:</p> <div> <div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>1</code></td> <td><code>(psil '(4 5 +))</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>2</code></td> <td><code>;=> (+ 5 4)</code></td> </tr> </tbody> </table> </div> </div> </div> <p>这回它就没有被执行,但却反转了。要想运行它并不困难:</p> <div> <div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>1</code></td> <td><code>(</code><code>eval</code> <code>(psil '(4 5 +)))</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>2</code></td> <td><code>;=> 9</code></td> </tr> </tbody> </table> </div> </div> </div> <p>你开始发现Lisp的强大之处了。事实上,Lisp代码就是一堆层层嵌套的列表序列,你可以很容易从这些序列数据中产生可以运行的程序。</p> <p>如果你还没明白,你可以在你常用的语言中试一下。在数组里放入2个数和一个加号,通过数组来执行这个运算。你最终得到的很可能是一个被连接的字符串,或是其它怪异的结果。</p> <p>这种编程方式在Lisp是如此的非常的常见,于是Lisp就提供了叫做<em>宏(macro)</em>的可重用的东西来抽象出这种功能。宏是一种函数,它接受未执行的参数,而返回的结果是可执行的Lisp代码。</p> <p>让我们把psil传化成宏:</p> <div> <div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>1</code></td> <td><code>(</code><code>defmacro</code> <code>psil </code><code>[</code><code>exp</code><code>]</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>2</code></td> <td><code> </code><code>(reverse exp))</code></td> </tr> </tbody> </table> </div> </div> </div> <p>唯一不同之处是我们现在使用<code>defmacro</code>来替换<code>defn</code>。这是一个非常大的改动:</p> <div> <div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>1</code></td> <td><code>(psil (4 5 +))</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>2</code></td> <td><code>;=> 9</code></td> </tr> </tbody> </table> </div> </div> </div> <p>请注意,虽然参数并不是一个有效的Clojure参数,但程序并没有报错。这是因为参数并没有被执行,只有当psil处理它时才被执行。<code>psil</code>把它的参数按数据看待。如果你听说过有人说Lisp里代码就是数据,这就是我们现在在讨论的东西了。数据可以被编辑,产生出其它的程序。这种特征使你可以在Lisp语言上创建出任何你需要的新型语法语言。</p> <p>在Clojure里有一种操作符叫做<code>macroexpand</code>,它可以使一个宏跳过可执行部分,这样你就能看到是什么样的代码将会被执行:</p> <div> <div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>1</code></td> <td><code>(macroexpand '(psil (4 5 +)))</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>2</code></td> <td><code>;=> (+ 5 4)</code></td> </tr> </tbody> </table> </div> </div> </div> <p>你可以把宏看作一个在编译期运行的函数。事实上,在Lisp里,编译期和运行期是杂混在一起的,你的程序可以在这两种状态下来回切换。我们可以让psil宏变的罗嗦些,让我们看看代码是如何运行的,但首先,我要先告诉你<code>do</code>这个东西。</p> <p><code>do</code>是一个很简单的操作符,它接受一批语句,依次运行它们,但这些语句是被整体当作一个表达式,例如:</p> <div> <div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>1</code></td> <td><code>(</code><code>do</code> <code>(</code><code>println</code> <code>"Hello"</code><code>) (</code><code>println</code> <code>"world"</code><code>))</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>2</code></td> <td><code>; Hello</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>3</code></td> <td><code>; world</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>4</code></td> <td><code>;=> nil</code></td> </tr> </tbody> </table> </div> </div> </div> <p>通过使用do,我们可以使宏返回多个表达式,我们能看到更多的东西:</p> <div> <div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>1</code></td> <td><code>(</code><code>defmacro</code> <code>psil </code><code>[</code><code>exp</code><code>]</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>2</code></td> <td><code> </code><code>(</code><code>println</code> <code>"compile time"</code><code>)</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>3</code></td> <td><code> </code><code>`(</code><code>do</code> <code>(</code><code>println</code> <code>"run time"</code><code>)</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>4</code></td> <td><code> </code><code>~(reverse exp)))</code></td> </tr> </tbody> </table> </div> </div> </div> <p>新宏会打印出“compile time”,并且返回一个<code>do</code>代码块,这个代码块打印出“run time”,并且反着运行一个表达式。这个反引号<code>`</code>的作用很像引号<code>'</code>,但它的独特之处是你可以使用<code>~</code>符号在其内部解除引号。如果你听不明白,不要担心,让我们来运行它一下:</p> <div> <div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>1</code></td> <td><code>(psil (4 5 +))</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>2</code></td> <td><code>; compile time</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>3</code></td> <td><code>; run time</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>4</code></td> <td><code>;=> 9</code></td> </tr> </tbody> </table> </div> </div> </div> <p>如预期的结果,编译期发生在运行期之前。如果我们使用<code>macroexpand</code>,或得到更清晰的信息:</p> <div> <div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>1</code></td> <td><code>(macroexpand '(psil (4 5 +)))</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>2</code></td> <td><code>; compile time</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>3</code></td> <td><code>;=> (</code><code>do</code> <code>(clojure.core/</code><code>println</code> <code>"run time"</code><code>) (+ 5 4))</code></td> </tr> </tbody> </table> </div> </div> </div> <p>可以看出,编译阶段已经发生,得到的是一个将要打印出“run time”的语句,然后会执行<code>(+ 5 4)</code>。<code>println</code>也被扩展成了它的完整形式,<code>clojure.core/println</code>,不过你可以忽略这个。然后代码在运行期被执行。</p> <p>这个宏的输出本质上是:</p> <div> <div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>1</code></td> <td><code>(</code><code>do</code> <code>(</code><code>println</code> <code>"run time"</code><code>)</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>2</code></td> <td><code> </code><code>(+ 5 4))</code></td> </tr> </tbody> </table> </div> </div> </div> <p>而在宏里,它需要被写成这样:</p> <div> <div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>1</code></td> <td><code>`(</code><code>do</code> <code>(</code><code>println</code> <code>"run time"</code><code>)</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>2</code></td> <td><code> </code><code>~(reverse exp))</code></td> </tr> </tbody> </table> </div> </div> </div> <p>反引号实际上是产生了一种模板形式的代码,而波浪号让其中的某些部分被执行(<code>(reverse exp)</code>),而其余部分被保留。</p> <p>对于宏,其实还有更令人惊奇的东西,但现在,它已经很能变戏法了。</p> <p>这种技术的力量还没有被完全展现出来。按着" 为什么我喜欢Smalltalk?"的思路,我们假设Clojure里没有<code>if</code>语法,只有<code>cond</code>语法。也许在这里,这并不是一个太好的例子,但这个例子很简单。</p> <p><code>cond</code> 功能跟其它语言里的<code>switch</code> 或 <code>case</code> 很相似:</p> <div> <div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>1</code></td> <td><code>(</code><code>cond</code> <code>(= x 0) </code><code>"It's zero"</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>2</code></td> <td><code> </code><code>(= x 1) </code><code>"It's one"</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>3</code></td> <td><code> </code><code>:else</code> <code>"It's something else"</code><code>)</code></td> </tr> </tbody> </table> </div> </div> </div> <p>使用 <code>cond</code>,我们可以直接创建出<code>my-if</code>函数:</p> <div> <div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>1</code></td> <td><code>(</code><code>defn</code> <code>my-</code><code>if</code> <code>[</code><code>predicate </code><code>if</code><code>-true </code><code>if</code><code>-false</code><code>]</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>2</code></td> <td><code> </code><code>(</code><code>cond</code> <code>predicate </code><code>if</code><code>-true</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>3</code></td> <td><code> </code><code>:else</code> <code>if</code><code>-false))</code></td> </tr> </tbody> </table> </div> </div> </div> <p>初看起来似乎好使:</p> <div> <div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>1</code></td> <td><code>(my-</code><code>if</code> <code>(= 0 0) </code><code>"equals"</code> <code>"not-equals"</code><code>)</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>2</code></td> <td><code>;=> </code><code>"equals"</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>3</code></td> <td><code>(my-</code><code>if</code> <code>(= 0 1) </code><code>"equals"</code> <code>"not-equals"</code><code>)</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>4</code></td> <td><code>;=> </code><code>"not-equals"</code></td> </tr> </tbody> </table> </div> </div> </div> <p>但有一个问题。你能发现它吗?<code>my-if</code>执行了它所有的参数,所以,如果我们像这样做,它就不能产生预期的结果了:</p> <div> <div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>1</code></td> <td><code>(my-</code><code>if</code> <code>(= 0 0) (</code><code>println</code> <code>"equals"</code><code>) (</code><code>println</code> <code>"not-equals"</code><code>))</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>2</code></td> <td><code>; equals</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>3</code></td> <td><code>; not-equals</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>4</code></td> <td><code>;=> nil</code></td> </tr> </tbody> </table> </div> </div> </div> <p>把<code>my-if</code>转变成宏:</p> <div> <div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>1</code></td> <td><code>(</code><code>defmacro</code> <code>my-</code><code>if</code> <code>[</code><code>predicate </code><code>if</code><code>-true </code><code>if</code><code>-false</code><code>]</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>2</code></td> <td><code> </code><code>`(</code><code>cond</code> <code>~predicate ~</code><code>if</code><code>-true</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>3</code></td> <td><code> </code><code>:else</code> <code>~</code><code>if</code><code>-false))</code></td> </tr> </tbody> </table> </div> </div> </div> <p>问题解决了:</p> <div> <div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>1</code></td> <td><code>(my-</code><code>if</code> <code>(= 0 0) (</code><code>println</code> <code>"equals"</code><code>) (</code><code>println</code> <code>"not-equals"</code><code>))</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>2</code></td> <td><code>; equals</code></td> </tr> </tbody> </table> </div> <div> <table class="ke-zeroborder"> <tbody> <tr> <td><code>3</code></td> <td><code>;=> nil</code></td> </tr> </tbody> </table> </div> </div> </div> <p>这只是对宏的强大功能的窥豹一斑。一个非常有趣的案例是,当面向对象编程被发明出来后(Lisp的出现先于这概念),Lisp程序员想使用这种技术。</p> <p>C程序员不得不使用他们的编译器发明出新的语言,C++和Object C。Lisp程序员却创建了一堆宏,就像defclass, defmethod等。这全都要归功于宏。变革,在Lisp里,只是一种进化。<br /> <br /> 本文转载自: 外刊IT评论 <a href="/misc/goto?guid=4958183272158702965" rel="nofollow">http://www.aqee.net/</a> </p>