当前Java领域最激动人心的事情莫过于可允许其它编程语言运行于Java虚拟机上。围绕JRuby、Groovy、Scala还有 Rhino(JavaScript引擎)的讨论已经甚嚣尘上。可为什么要墨守陈规呢?如果你真的想跳出主流,投身于一种与Java截然不同的的语言,Lisp就不失为一种很好的选择。现在已有几种可运行于JVM上的Lisp程序设计语言的开源实现,准备好开始我们的探索之旅吧!
Lisp有什么值得研究呢?首先,作为已有50年历史的语言,它促成许多被我们今日视为理所当然的观念。if-then-else结构、早期的面向对象和带垃圾回收的自动内存管理的尝试都来源于此。目前Java程序员的热点话题——词汇闭包(Lexical Closure),最初的探索也是七十年代在Lisp中展开的。除此以外,Lisp还具备其它许多语言至今都未采用的特性,这些出色的思想必将在未来引起复兴潮流。
本文的目标读者是有意了解Lisp的Java开发人员。我们将在接下来的内容中讨论当前可以用在JVM上的不同Lisp方言(dialect),令你快速了解Lisp程序设计工作机理和其独特之处,文章的最后会演示如何将Lisp代码与Java系统进行整合。
目前存在许多可用于不同平台的Lisp系统,有免费的也有商业的。对于想要开始探索Lisp的Java用户,不离开JVM是首选,这样的话起步很容易,还可以很方便的使用所有自己熟悉的Java库和相关工具。
Common Lisp和Scheme
Lisp有两种主要方言(dialect):Common Lisp和Scheme。虽然设计理念大体相似,但是它们的差别仍然足够引起孰优孰劣的激烈争论。
Common Lisp是1991年完成的ANSI标准。统一了几种早期Lisp的理念,是可用于多种应用开发的大型环境,其最为著名的应用是人工智能。而Scheme 产生于学术界,特意进行了精简化设计,经验证是一种很好的语言,既可用于计算机科学教学,又可以作为嵌入式脚本语言。你还可能会遇到其它一些比较有名的 Lisp:小型的特定于应用的DSLs,如Emacs Lisp或AutoCAD的AutoLISP。
上面提到的两种主要方言(dialect)在JVM上都有相应的实现,相较而言Schemes的实现要成熟一些。Armed Bear Common Lisp(www.armedbear.org/abcl.html)非常彻底的实现了Common Lisp标准,但它存在一个问题,如果你没有安装别的Common List系统,就不能构建分发版本,这对新手可能是个困难。
在Scheme方面,两个主要的产品是Kawa(www.gnu.org/software/kawa)和SISC(www.sisc-scheme.org——the Second Interpreter of Scheme Code)。在这篇文章的例子当中,我们会用到Kawa,它实际上是个框架,能创造可编译成Java字节码的新语言。Scheme只是它的实现之一。顺便说一句,Kawa的创建者Per Bothner目前就职于Sun,主要从事JavaFX项目的编译器方面的工作。
另外一个值得一提的竟争对手是Clojure(clojure.sourceforge.net)。这是一种新的语言,其Lisp方言(dialect)介于Scheme和Common Lisp之间。它是直接为JVM量身打造的,因此在上面提到的所有Lisp当中,有着最为清晰Java整合方案。它还具有其它一些激动人心的特性,例如内建的支持并发和事务内存。Clojure目前仍然处于探索测试阶段,因此在它基础上构建程序还有些为时尚早,但它绝对是一个值得关注的项目。
读取—求值—打印—循环
我们先来安装Kawa。它的分发版是一个单独的Jar文件,可以直接通过链接ftp://ftp.gnu.org/pub/gnu/kawa/kawa-1.9.1.jar下载。得到该Jar包后,就把它加进你的类路径上,这样你就可以通过运行如下命令启动REPL了:
java kawa.repl
#|kawa:1|#
该命令启动了Kawa,并显示一个提示符。这其中究竟有何奥妙呢?REPL(READ-EVAL-PRINT-LOOP)意思是读取—求值—打印—循环,这是与运行中的Lisp系统进行交互的方式——它“读取”你的输入,进行“求值”运算后,“打印”计算结果,如此反复“循环”。开发Lisp程序的方式,与我们开发Java程序时所遵循的“写代码、编译、运行”的周期不同。Lisp程序员需要激励他们的Lisp系统,保持它的运行状态,这样就令编译和运行时的界限模糊起来。在REPL中,函数和变量在执行过程中都是可以修改的,代码也是动态解释和编译的。
先来做点简单的事情:把两个数字加到一起。
#|kawa:1|# (+ 1 2)
3
这是Lisp表达式的典型结构或者说“格式”。语法都是一致的:表达式总被放在一对圆括号内,因为用的是前缀符号,所以“+”号要放在两个参量前。再来一个复杂点的结构,把几个格式嵌套在一起,建立一个树状结构:
#|kawa:2|# (* (+ 1 2) (- 3 4))
-3
Scheme的内建函数以同种机理工作:
#|kawa:3|# (if (> (string-length "Hello world") 5)
(display "Longer than 5 characters"))
Longer than 5 characters
上面程序中,用一个if语句来检查某一特定字符串的长度是否超过5个字符,如果像例子中的那样检查结果为真,就会执行紧随其后的表达式,该语句将会打印一条提示信息。注意这里的缩进只是为了增加可读性,如果你愿意的话,可以在一行内写下所有的语句。
Lisp代码用的这种括号密集(parenthesis-heavy)的风格也称为“S表达式(s-expressions)”。它可兼作定义结构化数据的通用方法,就像XML一样。Lisp有很多内建的函数,你可以很方便的应用S表达式格式操纵数据,这种便利转而促成Lisp的另外一个强大优势:既然语法是如此简单,那么编写产生、修改代码的程序也要比其它语言简单得多。当我们演示宏(macros)的例子时,会了解到更多类似情况。