Clojure 是下一个二十年的编程语言,在睿智的 Rich Hickey 撮合下 LISP 和 Java 技术完美结合的产物,兼具函数编程和 Java 平台的优势,聚焦于低错和易用的并发编程,同时足以完成一个通用编程语言的各种任务。虽然早就关注 Clojure,也断断续续的玩过,不过最近开始准备花多一点时间在这上面,如果可能还想翻译或者写本书。不过这次只记录下怎么搭建和维护一个 Mac OS X 下的 Clojure 学习环境(还不能叫工作环境)。

Before the Beginning

– Clojure 是一个基于 JVM 的编程语言,其开发、运行都和 Java 平台有着密切的联系,一个可靠、高效的 JVM 自然必不可少,Mac OS X 内置的 Java 6 SDK 就不错;
– 有了 JVM 之后,一组紧凑的 Java 代码加上用 Clojure 自己语法编写的核心库,就构成了这个非常简洁的语言环境,所以运行 Clojure 还是很容易就可以实现的,但是——
– 除此之外,Clojure 是严格意义上的函数编程语言,是 LISP-1 类型的方言,所以它的运行模式也是典型的 REPL (Read-Evaluate-Print-Loop),Clojure 自己包含了一个 REPL,但是凡是玩过 SLIME (Superior Lisp Interaction Mode for Emacs) 或者至少看过那个著名的「Marco Baringer’s SLIME Tutorial」的人都一定想用 SLIME 来跑 Clojure,幸运的是真有人做了 Clojure 的 SLIME 后端,配置起来虽然稍微有点麻烦,但是绝对值得!

Java VM

使用系统自带的就可以,当然喜欢玩的朋友可以装 OpenJDK 玩,反正以后 Mac 上的 Java 都会走那边发布了。我的环境下:

% export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Home
% java -version
java version "1.6.0_26"
Java(TM) SE Runtime Environment (build 1.6.0_26-b03-383-11A511)
Java HotSpot(TM) 64-Bit Server VM (build 20.1-b02-383, mixed mode)

Clojure & Clojure Contrib

Clojure 目前划分为两个大的部分来分发,一部分是 Clojure Core,是语言的核心和最紧凑的基本功能;另一部分是 Clojure Contrib,来自社区开发的各种常用库,包含很多个小 Module,覆盖各种常用功能。它们都可以在这里下载,一般就用最新的稳定版本比较好(注意 Clojure Contrib 和 Clojure Core 的版本必须相同,不可混用),喜欢追新和有志于参与 Clojure 本身开发的可以从官方的 GitHub Repo 克隆一份到本地再自己编译(注意,编译 Clojure Core 需要 Apache Ant,编译 Clojure Contrib 需要 Apache Maven 2):

% export CLOJURE_HOME=~/Code/Clojure
% cd $CLOJURE_HOME
% git clone https://github.com/clojure/clojure.git
% cd clojure
% ant
% cd ../
% git clone https://github.com/clojure/clojure-contrib.git
% cd clojure-contrib
% mvn install

当你手上有了需要的 clojure.jarclojure-contrib.jar 两个文件之后,可以这样运行 Clojure 的 REPL(假定 $CLOJURE_JAR$CONTRIB_JAR 两个环境变量指向两个 JAR 包的路径):

% java -cp $CLOJURE_JAR:$CONTRIB_JAR clojure.main

或者这样来运行一个 Clojure 的源文件(一般以 .clj 作为扩展名):

% java -cp $CLOJURE_JAR:$CONTRIB_JAR clojure.main Fib.clj

如果喜欢折腾,可以用 rlwrap 鼓捣出一个支持行编辑、支持 Tab 自动补全的简单 REPL 来,可以参考这里的教程

这些都不是重点,因为我们要以 Emacs 和 SLIME 为核心来构建我们的环境,下面开始。

Emacs

Mac OS X 内置 Emacs,不过版本较老(22.1.1),而且只有字符版本,比较不方便,比较推荐的办法是自己编译一个 Cocoa 的版本,漂亮且好用。

% cd ~/Code/Emacs
% bzr branch bzr://bzr.savannah.gnu.org/emacs/trunk
% cd trunk
% ./autogen.sh
% ./configure --with-ns
% make bootstrap && make install
% mv nextstep/Emacs.app /Applications

在 Applications 下就有一个 Emacs 的 Cocoa 版本可以用了。

.emacs

每个 Emacs 用户都有自己精心维护的 .emacs 文件,里面拉拉杂杂无数东西,和启用的各种 Emacs modes 有关,所以一般都很难通用。我的可以在这里看到,仅供参考。如果以前没玩过 Emacs,这里就运行下然后退出,让它自己生成一个空的 .emacs 好了。

swank-clojure

swank 是 SLIME 的后端,SLIME 作为一个 LISP 的前端 REPL 和调试界面,所有真正运行代码的工作是由后台的 swank 程序完成的,这样才使得 SLIME 一套代码、一套界面可以服务于各种不同的 LISP 方言。现在有人提供了 swank-clojure 的后端,自然也可以支持 Clojure 语言了。

swank-clojure 和 clojure-mode 最早都由 Jeffrey Chu 开发,早期我遇到问题还邮件咨询过他,是个很友善的 hacker,不过后来开发进展比较慢,有不少兼容问题(因为 Clojure 和 SLIME 都是开发非常活跃,每个月都有新版本的项目),就有了不少 GitHub 上的 fork 版本,目前开发最活跃,也很可能是今后主力的是 technomancy(Phil Hagelberg,@technomancy)的分支,下面的描述都基于此分支。

最早的 swank-clojure 提供 Emacs 本地运行的方式,但目前都推荐单独运行一个进程,其安装和运行需要 Clojure 的配置管理工具 Leiningen(也是 technomancy 的作品):

% cd ~/bin
% curl https://raw.github.com/technomancy/leiningen/stable/bin/lein -o lein
% chmod +x lein
% cd
% lein plugin install swank-clojure 1.3.3
% export LEIN_HOME="~/.lein"
% export PATH="$PATH:$LEIN_HOME/bin"
% swank-clojure

这会下载并安装 swank-clojure 以及所有依赖文件,这些东西都会安装在 ~/.m2/repository 下(这是 Maven 的标准配置,Leiningen 沿用 Maven 2 的很多惯例),包括 1.2.0 正式版本的 Clojure Core 和 Clojure Contrib;最后这个命令会启动 swank-clojure 的后端进程,在缺省的 4005 端口等待 SLIME 前端的连接(等下我们会从 Emacs 里用 slime-connect 来连接)。

在继续之前可以查看下 ~/.lein/bin/swank-clojure,为了方便加入 Clojure-Contrib 以及其它库或者改用不同版本,可以把它改成下面的样子:

#!/bin/sh

# This script was automatically generated by Leiningen.

M2_REPO_HOME="$HOME/.m2/repository"
CLOJURE_HOME="$HOME/Code/Clojure"

SWANK_CLOJURE_JAR="$M2_REPO_HOME/swank-clojure/swank-clojure/1.3.2/swank-clojure-1.3.2.jar"
CLOJURE_JAR="$CLOJURE_HOME/clojure-1.2.1/clojure.jar"
# CLOJURE_JAR="$CLOJURE_HOME/clojure-1.3.0/clojure-1.3.0.jar"
CLOJURE_CONTRIB_JAR="$CLOJURE_HOME/clojure-contrib-1.2.0/target/clojure-contrib-1.2.0.jar"

CLASSPATH="$SWANK_CLOJURE_JAR:$CLOJURE_JAR:$CLOJURE_CONTRIB_JAR"
# CLASSPATH="$SWANK_CLOJURE_JAR:$CLOJURE_JAR"
NULL_DEVICE=/dev/null
JVM_OPTS=${JVM_OPTS:-$JAVA_OPTS}
MAIN="swank.swank"
VERSION="1.3.2"

if [ "$OSTYPE" = "cygwin" ]; then
    CLASSPATH=`cygpath -wp "$CLASSPATH"`
    NULL_DEVICE=NUL
fi

java -cp "$CLASSPATH" $JVM_OPTS -Dproject.version=$VERSION \
    clojure.main -e "(use '$MAIN)(apply -main *command-line-args*)" $NULL_DEVICE "$@"

SLIME

SLIME 一般的安装方式是到其主页下载最新的 CVS 版本使用,不过因为 Clojure 和 LISP 有些微妙的差异,而且本身还是个比较新的语言,关键机制的改变也比较多见,所以 swank-clojure 的实现经常赶不上 SLIME 最新的变化,于是 technomancy 维护了一个 SLIME 的分支,用来保持稳定在和最新版本 swank-clojure 能够协同工作的状态。technomancy 比较建议采用 Emacs 23 以后内置的包管理工具 ELPA 来安装这个版本的 SLIME,但是经我多次试验,总是有些问题,所以还是采用手工方式:

% cd ~/.emacs.d
% git clone https://github.com/technomancy/clojure-mode.git clojure-mode
% git clone https://github.com/technomancy/slime.git slime

然后在 .emacs 文件中加入以下代码:

;; Load clojure-mode
(add-to-list 'load-path "~/.emacs.d/clojure-mode/")
(require 'clojure-mode)

;; Load and set up slime
(add-to-list 'load-path "~/.emacs.d/slime/")
(require 'slime)

;; If another LISP used with SLIME, uncomment the line below and 
;; set the LISP program name & path
; (add-to-list 'slime-lisp-implementations '(sbcl ("/usr/local/bin/sbcl"))) 

(eval-after-load "slime"
	'(slime-setup '(slime-fancy slime-banner)))

(add-hook 'lisp-mode-hook
	(lambda ()
	(cond ((not (featurep 'slime))
	  (require 'slime) 
		(normal-mode)))))

;; Syntax highlighting in the slime repl only for swank-clojure
(add-hook 'slime-repl-mode-hook 'clojure-mode-font-lock-setup)

Connect and Hack

现在可以从 Applications 里运行 Emacs,如果一切顺利,输入 M-x slime-connect,接受缺省的主机和端口,如果提示前后端版本不一致是否还是要连接,选择 [y],就能连接上前面我们启动的 swank-clojure 后端进程,进入 SLIME 了。

Happy hacking!