【外刊IT评论网】为什么我喜欢Lisp语言

标签: lisp 技术技巧 | 发表时间:2011-10-14 00:26 | 作者:Aqee EK
出处:http://www.aqee.net
本文是从 Why I love Lisp 这篇文章翻译而来。

这篇文章是我在Simplificator——我工作的地方——的一次座谈内容的摘录,座谈的题目叫做“为什么我喜欢Smalltalk语言和Lisp语言”。在此之前,我曾发布过一篇叫做“ 为什么我喜欢Smalltalk?”的文章。

大漠黄沙 by Guilherme Jófili

Lisp是一种很老的语言。非常的老。Lisp有很多变种,但如今已没有一种语言叫Lisp的了。事实上,有多少Lisp程序员,就有多少种Lisp。这是因为,只有当你独自一人深入荒漠,用树枝在黄沙上为自己喜欢的Lisp方言写解释器时,你才成为一名真正的Lisp程序员

目前主要有两种Lisp语言分支:Common LispScheme,每一种都有无数种的语言实现。各种Common Lisp实现都大同小异,而各种Scheme实现表现各异,有些看起来非常的不同,但它们的基本规则都相同。这两种语言都非常有趣,但我却没有在实际工作中用过其中的任何一种。这两种语言中分别在不同的方面让我苦恼,在所有的Lisp方言中,我最喜欢的是Clojure语言。我不想在这个问题上做更多的讨论,这是个人喜好,说起来很麻烦。

Clojure,就像其它种的Lisp语言一样,有一个REPL(Read Eval Print Loop)环境,你可以在里面写代码,而且能马上得到运行结果。例如:

1 5
2 ;=> 5
3
4 "Hello world"
5 ;=> "Hello world"

通常,你会看到一个提示符,就像user>,但在本文中,我使用的是更实用的显示风格。这篇文章中的任何REPL代码你都可以直接拷贝到Try Clojure运行。

我们可以像这样调用一个函数:

1 (println "Hello World")
2 ; Hello World
3 ;=> nil

程序打印出“Hello World”,并返回nil。我知道,这里的括弧看起来好像放错了地方,但这是有原因的,你会发现,他跟Java风格的代码没有多少不同:

1 println("Hello World")

这种Clojure在执行任何操作时都要用到括弧:

1 (+ 1 2)
2 ;=> 3

在Clojure中,我们同样能使用向量(vector):

1 [1 2 3 4]
2 ;=> [1 2 3 4]

还有符号(symbol):

1 'symbol
2 ;=> symbol

这里要用引号('),因为Symbol跟变量一样,如果不用引号前缀,Clojure会把它变成它的值。list数据类型也一样:

1 '(li st)
2 ;=> (li st)

以及嵌套的list:

1 '(l (i s) t)
2 ;=> (l (i s) t)

定义变量和使用变量的方法像这样:

1 (def hello-world "Hello world")
2 ;=> #'user/hello-world
3
4 hello-world
5 ;=> "Hello world"

我的讲解会很快,很多细节问题都会忽略掉,有些我讲的东西可能完全是错误的。请原谅,我尽力做到最好。

在Clojure中,创建函数的方法是这样:

1 (fn [n] (* n 2))
2 ;=> #<user$eval1$fn__2 user$eval1$fn__2@175bc6c8>

这显示的又长又难看的东西是被编译后的函数被打印出的样子。不要担心,你不会经常看到它们。这是个函数,使用fn操作符创建,有一个参数n。这个参数和2相乘,并当作结果返回。Clojure和其它所有的Lisp语言一样,函数的最后一个表达式产生的值会被当作返回值返回。

如果你查看一个函数如何被调用:

1 (println "Hello World")

你会发现它的形式是,括弧,函数,参数,反括弧。或者用另一种方式描述,这是一个列表序列,序列的第一位是操作符,其余的都是参数。

让我们来调用这个函数:

1 ((fn [n] (* n 2)) 10)
2 ;=> 20

我在这里所做的是定义了一个匿名函数,并立即应用它。让我们来给这个函数起个名字:

1 (def twice (fn [n] (* n 2)))
2 ;=> #'user/twice

现在我们通过这个名字来使用它:

1 (twice 32)
2 ;=> 64

正像你看到的,函数就像其它数据一样被存放到了变量里。因为有些操作会反复使用,我们可以使用简化写法:

1 (defn twice [n] (* 2 n))
2 ;=> #'user/twice
3
4 (twice 32)
5 ;=> 64

我们使用if来给这个函数设定一个最大值:

1 (defn twice [n] (if (> n 50) 100 (* n 2))))

if操作符有三个参数:断言,当断言是true时将要执行的语句,当断言是 false 时将要执行的语句。也许写成这样更容易理解:

1 (defn twice [n]
2   (if (> n 50)
3       100
4       (* n 2)))

非常基础的东西。让我们来看一下更有趣的东西。

假设说你想把Lisp语句反着写。把操作符放到最后,像这样:

1 (4 5 +)

我们且把这种语言叫做Psil(反着写的Lisp...我很聪明吧)。很显然,如果你试图执行这条语句,它会报错:

1 (4 5 +)
2 ;=> java.lang.ClassCastException: java.lang.Integer cannot be cast to clojure.lang.IFn (NO_SOURCE_FILE:0)

Clojure会告诉你4不是一个函数(函数是必须是clojure.lang.IFn接口的实现)。

我们可以写一个简单的函数把Psil转变成Lisp:

1 (defn psil [exp]
2   (reverse exp))

当我执行它时出现了问题:

1 (psil (4 5 +))
2 ;=> java.lang.ClassCastException: java.lang.Integer cannot be cast to clojure.lang.IFn (NO_SOURCE_FILE:0)

很明显,我弄错了一个地方,因为在psil被调用之前,Clojure会先去执行它的参数,也就是(4 5 +),于是报错了。我们可以显式的把这个参数转化成list,像这样:

1 (psil '(4 5 +))
2 ;=> (+ 5 4)

这回它就没有被执行,但却反转了。要想运行它并不困难:

1 (eval (psil '(4 5 +)))
2 ;=> 9

你开始发现Lisp的强大之处了。事实上,Lisp代码就是一堆层层嵌套的列表序列,你可以很容易从这些序列数据中产生可以运行的程序。

如果你还没明白,你可以在你常用的语言中试一下。在数组里放入2个数和一个加号,通过数组来执行这个运算。你最终得到的很可能是一个被连接的字符串,或是其它怪异的结果。

这种编程方式在Lisp是如此的非常的常见,于是Lisp就提供了叫做宏(macro)的可重用的东西来抽象出这种功能。宏是一种函数,它接受未执行的参数,而返回的结果是可执行的Lisp代码。

让我们把psil传化成宏:

1 (defmacro psil [exp]
2   (reverse exp))

唯一不同之处是我们现在使用defmacro来替换defn。这是一个非常大的改动:

1 (psil (4 5 +))
2 ;=> 9

请注意,虽然参数并不是一个有效的Clojure参数,但程序并没有报错。这是因为参数并没有被执行,只有当psil处理它时才被执行。psil把它的参数按数据看待。如果你听说过有人说Lisp里代码就是数据,这就是我们现在在讨论的东西了。数据可以被编辑,产生出其它的程序。这种特征使你可以在Lisp语言上创建出任何你需要的新型语法语言。

在Clojure里有一种操作符叫做macroexpand,它可以使一个宏跳过可执行部分,这样你就能看到是什么样的代码将会被执行:

1 (macroexpand '(psil (4 5 +)))
2 ;=> (+ 5 4)

你可以把宏看作一个在编译期运行的函数。事实上,在Lisp里,编译期和运行期是杂混在一起的,你的程序可以在这两种状态下来回切换。我们可以让psil宏变的罗嗦些,让我们看看代码是如何运行的,但首先,我要先告诉你do这个东西。

do是一个很简单的操作符,它接受一批语句,依次运行它们,但这些语句是被整体当作一个表达式,例如:

1 (do (println "Hello") (println "world"))
2 ; Hello
3 ; world
4 ;=> nil

通过使用do,我们可以使宏返回多个表达式,我们能看到更多的东西:

1 (defmacro psil [exp]
2   (println "compile time")
3   `(do (println "run time")
4        ~(reverse exp)))

新宏会打印出“compile time”,并且返回一个do代码块,这个代码块打印出“run time”,并且反着运行一个表达式。这个反引号`的作用很像引号',但它的独特之处是你可以使用~符号在其内部解除引号。如果你听不明白,不要担心,让我们来运行它一下:

1 (psil (4 5 +))
2 ; compile time
3 ; run time
4 ;=> 9

如预期的结果,编译期发生在运行期之前。如果我们使用macroexpand,或得到更清晰的信息:

1 (macroexpand '(psil (4 5 +)))
2 ; compile time
3 ;=> (do (clojure.core/println "run time") (+ 5 4))

可以看出,编译阶段已经发生,得到的是一个将要打印出“run time”的语句,然后会执行(+ 5 4)println也被扩展成了它的完整形式,clojure.core/println,不过你可以忽略这个。然后代码在运行期被执行。

这个宏的输出本质上是:

1 (do (println "run time")
2     (+ 5 4))

而在宏里,它需要被写成这样:

1 `(do (println "run time")
2      ~(reverse exp))

反引号实际上是产生了一种模板形式的代码,而波浪号让其中的某些部分被执行((reverse exp)),而其余部分被保留。

对于宏,其实还有更令人惊奇的东西,但现在,它已经很能变戏法了。

这种技术的力量还没有被完全展现出来。按着" 为什么我喜欢Smalltalk?"的思路,我们假设Clojure里没有if语法,只有cond语法。也许在这里,这并不是一个太好的例子,但这个例子很简单。

cond 功能跟其它语言里的switchcase 很相似:

1 (cond (= x 0) "It's zero"
2       (= x 1) "It's one"
3       :else "It's something else")

使用 cond,我们可以直接创建出my-if函数:

1 (defn my-if [predicate if-true if-false]
2   (cond predicate if-true
3         :else if-false))

初看起来似乎好使:

1 (my-if (= 0 0) "equals" "not-equals")
2 ;=> "equals"
3 (my-if (= 0 1) "equals" "not-equals")
4 ;=> "not-equals"

但有一个问题。你能发现它吗?my-if执行了它所有的参数,所以,如果我们像这样做,它就不能产生预期的结果了:

1 (my-if (= 0 0) (println "equals") (println "not-equals"))
2 ; equals
3 ; not-equals
4 ;=> nil

my-if转变成宏:

1 (defmacro my-if [predicate if-true if-false]
2   `(cond ~predicate ~if-true
3          :else ~if-false))

问题解决了:

1 (my-if (= 0 0) (println "equals") (println "not-equals"))
2 ; equals
3 ;=> nil

这只是对宏的强大功能的窥豹一斑。一个非常有趣的案例是,当面向对象编程被发明出来后(Lisp的出现先于这概念),Lisp程序员想使用这种技术。

C程序员不得不使用他们的编译器发明出新的语言,C++和Object C。Lisp程序员却创建了一堆宏,就像defclass, defmethod等。这全都要归功于宏。变革,在Lisp里,只是一种进化。


本文来自外刊IT评论网(www.aqee.net),原始地址:为什么我喜欢Lisp语言

相关 [it 我喜欢 lisp] 推荐:

【外刊IT评论网】为什么我喜欢Lisp语言

- EK - 外刊IT评论
本文是从 Why I love Lisp 这篇文章翻译而来. 这篇文章是我在Simplificator——我工作的地方——的一次座谈内容的摘录,座谈的题目叫做“为什么我喜欢Smalltalk语言和Lisp语言”. 在此之前,我曾发布过一篇叫做“ 为什么我喜欢Smalltalk. 大漠黄沙 by Guilherme Jófili.

《Practical Common Lisp 中文版》样章

- 夜深 - Chun Tian (binghe)
(注:我的译作《Practical Common Lisp 中文版》将由人民邮电出版社图灵公司出版,不过由于种种原因目前该书还在紧张的编辑之中,最后确定的出版时间为 10 月底之前. 由于拖得比较久,个人感到愧对读者,因此经过和出版社的协商,现公开其中的一章供读者预览. 内容直接来自未经编辑的原始译稿,因此本文和最后出版的内容将在细节上有所出入,还请读者见谅) 第五章 函数 (英文原版).

使用Lisp搭建独立博客

- Linker Lin - loop_in_codes
使用Lisp搭建独立博客Author:Kevin LynxDate:9.29.2011Contact:kevinlynx at gmail dot com. 本文描述如何使用Lisp工具集搭建一个完整的个人博客站点. 一个搭建好的例子站点可以参看我的个人博客:http://codemacro.com.

《实用 Common Lisp 编程》译者序

- yat - Chun Tian (binghe)
最近忙得脚打后脑勺,但博客还要持续更新,所以无奈之下只好把我给《实用 Common Lisp 编程》一书撰写的译者序重新发表在这里,以方便那些尚未读过该书的潜在读者们. 说实话,今天重读了一遍以后,我被我自己写的文字给深深打动了,因为我的写作能力很不稳定,一篇好文不是随时都可以写得出来的. 也许这就是会写字的人和职业写手的区别所在吧.

为什么我喜欢Java

- - ImportNew
我现在的老板使用一个在线测试系统来筛选在线申请职位的求职者. 测试的第一个问题很浅显,仅仅是为了让求职者熟悉一下这个系统的提交和测试代码的流程. 问题是这样的,写一个将标准输入拷贝到标准输出的流程. 求职者可以使用任何他们喜欢的编程语言来实现这个程序. 有时候我们也会遇到在这个测试中得零分的求职者.

至今听到关于 Lisp 最迷人的故事

- Ben - 译言-每日精品译文推荐
来源Amazing Lisp story ever heared. 在 ILC 2002 大会上前Lisp大神,当今的Python狂热者Peter Norvig,由于某些原因,做一个类似于马丁路德在梵蒂冈宣扬新教的主题演讲,因为他在演讲中大胆地声称Python就是Lisp. 讲完后进入提问环节,出乎我意料的是,Peter点了我过道另一侧,靠上面几排座位的一个老头,他衣着皱褶,在演讲刚开始的时候踱步进来,然后就靠在了那个座位上面.

在 SBCL 等开源 Lisp 平台上运行 CL-HTTP (part 1)

- 旺旺 - Chun Tian (binghe)
现在我来说明在 4 种开源 Common Lisp 平台上运行 CL-HTTP 的方法,四种平台分别是 SBCL、Clozure CL、CMUCL 和 Macintosh Common Lisp (MCL). 我相信我所提供的这些信息对某些 Common Lisp 爱好者来说将是梦寐以求的. 过去我也曾在博客里多次提到 CL-HTTP,多年来也一直在实际地学习和使用这个软件.

在 SBCL 等开源 Lisp 平台上运行 CL-HTTP (part 2)

- Sungelina - Chun Tian (binghe)
在 Clozure CL 上启动 CL-HTTP. CL-HTTP 在 Clozure CL (CCL) 上跑得比 SBCL 更好一些,因为 CCL 的多线程 API 特性更加丰富,与 CL-HTTP 的可移植兼容层的吻合度也比较高. CCL 是我最喜爱的开源 Common Lisp 实现. 它由商业公司维护,性能稳定可靠,并且在所有支持的 OS/CPU 组合上都有多线程支持以及 32/64 位版本 (ARM 除外,只有 32 位),在 Mac OS X 上甚至还有一个 IDE.

Lispの創案者、ジョン・マッカーシー逝去(84歳)

- 三十不归 - TechCrunch Japan
Lispの創案者であり、現代人工知能の父ともいうべきジョン・マッカーシー(John McCarthy)が今日(米国時間10/24)逝去した. マッカーシーはプリンストンで〔映画「ビューティフルマインド」のモデルにもなったノーベル賞受賞者〕有名なジョン・ナッシュの下で数学を学び、その後、アメリカと当時のソ連の科学者の間で世界で最初のコンピュータ同士のチェス対局を実現させた.