使用 Emacs 开发 Clojure

本作品遵循 Creative Commons Attribution 3.0 Unported License 许可协议(包括图像和表格)。其源码可以在 Github 上获取。

本指南涵盖哪些版本的 Clojure?

本指南涵盖 Clojure 1.5 以上版本及 Emacs 24.x 版。早期的 Clojure 和 Emacs 发行版将不受支持。

概述

Emacs 历来是最好的函数式编程语言开发环境之一,对于 Lisp 语言及其方言来说尤其如此。本指南将讲解如何安装它,并给出用其开发一个简单的库的一个基本工作流程的例子。

安装 Emacs

OSX

到目前为止,在 OSX 上获取 Emacs 最简单的方式就是使用 Homebrew 了。这里是一个安装说明。另外你还需要确保你已经为 Homebrew 安装了 XCode 来使其正常工作。

如果已经安装了 brew,那么你就可以使用以下命令来安装 Emacs 24 了:


 
  1. $ brew install emacs --with-cocoa
  2. $ brew linkapps Emacs

这应该会安装 Emacs 的最新版本,并将 Emacs.app 符号链接到你的 ~/Applications 文件夹下。

编译之后,Emacs 将会顺利的存在于你的 cellar 中。你可以检查一下:


 
  1. $ ls /usr/local/Cellar/emacs/24.x

如果你需要自定义你的环境的话(在 .profile 文件或你的 shell-specific 配置中)你可以添加这个函数来解决当从 OS X 的 GUI 中启动 Emacs 时的路径问题(多亏了 Steve Purcell 将其提供在了 Clojure 邮件列表中):


 
  1. ;; fix the PATH variable
  2. (defun set-exec-path-from-shell-PATH ()
  3. (let ((path-from-shell (shell-command-to-string "TERM=vt100 $SHELL -i -c 'echo $PATH'")))
  4. (setenv "PATH" path-from-shell)
  5. (setq exec-path (split-string path-from-shell path-separator))))
  6. (when window-system (set-exec-path-from-shell-PATH))

这可以确保在你的 PATH 下拥有的所有东西都会在使用 GUI 的 Emacs 时被实际支持,无论你如何启动它。

还有一个叫做 exec-path-from-shell 的软件包可以自动完成这个功能。建议 OS X 的用户安装它!

Debian/Ubuntu

较新的基于 Debian 的系统(post-wheezy)可使用 apt 中的 Emacs 24:


 
  1. $ sudo aptitude install emacs24

在旧版系统中你可以为 emacs-snapshot 添加非官方的软件包源,无论是 Debian 或 Ubuntu

MS Windows

你可以从自由软件基金会 FTP 目录中找到 Windows 版的 Emacs。

下载名为 emacs-24.1-bin-i386.zip 的文件,并将其解压到一个新的文件夹。避免文件夹中存在类似 C:/Documents and Settings 一样的空格。推荐将文件夹命名为类似 C:/emacs-24.1 这样。

创建一个叫做 HOME 的环境变量,其值相当于你的主(home)文件夹的位置;在 Windows XP 中,是 C:/Documents and Settings/YourUsername,在 Windows 7 中是 C:/Users/YourUsername。设置了这个变量之后,你就可以使用波浪号字符(~)键入你的主文件夹下的一个文件名,Emacs 将会扩充它的完整路径。

以下部分介绍使用 .emacs.d 文件夹配置 Emacs。当在 Windows 中使用 Emacs 时,你应该在你的主文件夹中创建该文件夹。在 Windows XP 中,其将是 C:/Documents and Settings/YourUsername/.emacs.d 这个文件夹;在 Windows 7 中,其将是 C:/Users/YourUsername/.emacs.d 这个文件夹。

配置 Emacs

Emacs 安装好了,但现在就运行它将只能得到一些基础的体验,尤其是不能用于 Clojure 开发。

手动设置

Emacs 可以通过你的主文件夹中的一个叫做 ~/.emacs.d 的文件夹进行配置,而且其配置选项几乎是无穷无尽的。为了帮助你完成这项工作,Phil Hagelberg 已经创建了一个开启了一些非侵入式的有用的功能的叫做 better-defaults 的小的库,如果你不是一个 Emacs 专家,这对你来说可能很有用。

大多数 Emacs 软件包被维护在了 MELPA 中,其是一个社区版软件包主机。将以下代码添加到你在 ~/.emacs.d/init.el 中的配置里,来告诉 Emacs 从那里寻找。

稳定版仓库:


 
  1. cl (require 'package) (add-to-list 'package-archives '("melpa-stable" . "http://stable.melpa.org/packages/") t) (package-initialize)

最新版仓库:


 
  1. cl (require 'package) (add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/") t) (package-initialize)

执行 M-x package-refresh-contents 来获取软件包列表。

M-x 代表 meta-x,在大多数键盘上 meta 被映射到了 Alt 键,虽然在 Mac OS X 中通常将其映射到 command 键。

你将需要安装下列软件包:

  • clojure-mode – 用于编辑 Clojure 和 ClojureScript 代码的主模式
  • CIDER – 一个 Clojure 的交互式开发环境,以及在 Emacs 运行 REPL
  • projectile(可选) – 用于快速在你的项目中导航

在进一步研究之前,你应该简单的参考一下它们的文档。

你既可以用 M-x package-install 命令一个接一个的安装每个软件包,也可以在 Emacs Lisp 中指定你所有的软件包做为你的配置文件的一部分。这有助于将你的 dotfiles 移动到新的机器;你不必记得你手工安装的所有东西。


 
  1. (defvar my-packages '(better-defaults
  2. projectile
  3. clojure-mode
  4. cider))
  5. (dolist (p my-packages)
  6. (unless (package-installed-p p)
  7. (package-install p)))

将以上代码放在 ~/.emacs.d/init.el 中,并使用 M-x eval-buffer 命令运行它。

可能会有很多警告(warnings)信息飞逝而过,因为它正在安装和编译软件包。除非你遇到一些实际的错误(errors)信息,否则这些都是正常的。

要查看其他可供安装的软件包,可以调用 M-x package-list-packages 命令。要手动安装一个软件包,要使用键盘将光标移动到软件包所在的行,然后按代表 ‘install’ 的 ‘i’ 键。在选择了你感兴趣的所有软件包后,按代表 ‘eXecute’ 的 ‘x’ 键进行安装。

预配置设置

这里也有一些现成的 Emacs 配置,为 Clojure 的开发进行了优化 – Prelude(由 CIDER 和 clojure-mode 的维护者开发) 和 Emacs Live

如果你需要一个更强大的 Emacs 配置,那么你一定要将他们检出。

基础知识

你应该做的第一件事毫无疑问就是查看内置的 Emacs 教程。要做到这一点请按 C-h t 快捷键或按住 Control 键再按 h 键再只按 t 键。

考虑到这一点,以下列出你会经常使用到的基本按键:


 
  1. 文件 / 缓冲区 / 窗口命令
  2. C-x C-f 找到文件
  3. C-x C-s 保存缓冲区
  4. C-x s 保存文件 (类似另存为…)
  5. C-x b 切换缓冲区
  6. C-x k 关闭缓冲区
  7. C-x 1 关闭其他窗口
  8. C-x 0 关闭当前窗口
  9. C-x 2 水平分割窗口
  10. C-x 3 垂直分割窗口
  11. 移动命令
  12. C-a 一行的开始
  13. C-e 一行的结尾
  14. C-n 后一行 (下)
  15. C-p 前一行 (上)
  16. C-b 后退 (左)
  17. C-f 前进 (右)
  18. M-f 以词为单位前进
  19. M-b 以词为单位后退
  20. C-v 向前翻页
  21. M-v 向后翻页
  22. 修改命令
  23. C-d 移除字符
  24. M-d 移除词
  25. M-delete 移除前一个词
  26. 其他命令
  27. C-s 正则表达式向后搜索
  28. C-r 正则表达式向前搜索
  29. M-% 查询替换

我还会提一下帮助命令:


 
  1. C-h t 教程 (重温基础知识)
  2. C-h b 描述所有当前绑定的快捷键
  3. C-h m 描述当前模式
  4. C-h a Apropos - 搜索帮助
  5. C-h k 描述键

我建议至少读一次教程,因为它会让你对导航和移动命令有很好的理解。你还将大量使用的另一个命令就是 M-x,它允许你执行任何命令,而且真的非常多。Apropos 对于使用 C-h a 搜索某个东西是非常有用的。

在读完教程后(你没有做到这一点,对不对?O_O)你可以移动光标、打开文件、保存文件等,通常可以基本适应了。学习 Emacs 是永无止境的,但这些基础知识将永远伴你左右。

创建一个项目

让我们通过创建一个小的 Clojure 示例项目的过程,来演示 Emacs 如何帮我们成为 Lisp 沃土的捍卫者。

我们要创建的项目是一个平常的使用传给它的参数来把它们转换成 map 中的键值对的简单命令行解析器。功能无关紧要,而且也没啥用。其目的只是为了演示开发流程。

如果你还没有 Leiningen,就安装它并使用它创建一个新的项目:


 
  1. $ lein new command-line-args
  2. $ cd command-line-args

搂一眼项目的结构:


 
  1. + doc
  2. - intro.md
  3. - project.clj
  4. - README.md
  5. + src
  6. + command_line_args
  7. - core.clj
  8. + test
  9. + command_line_args
  10. - core_test.clj

应该不用过多解释了,虽然 Leiningen 内置的教程(通过 lein help tutorial 命令查看)提供了对项目结构的详细解释。

在我们继续之前,我们必须使用 CIDER 来开发我们的项目,这需要进行一点一次性的设置。打开 project.clj 文件并在此添加 cider-nrepl 插件。该文件最初应该看起来像这样(或稍有不同):


 
  1. (defproject command-line-args "0.1.0-SNAPSHOT"
  2. :description "FIXME: write description"
  3. :url "http://example.com/FIXME"
  4. :license {:name "Eclipse Public License"
  5. :url "http://www.eclipse.org/legal/epl-v10.html"}
  6. :dependencies [[org.clojure/clojure "1.6.0"]])

你需要这样做(请记住,你现在读到本文时 cider-nrepl 可能已经有了更新的版本):


 
  1. (defproject command-line-args "0.1.0-SNAPSHOT"
  2. :description "FIXME: write description"
  3. :url "http://example.com/FIXME"
  4. :license {:name "Eclipse Public License"
  5. :url "http://www.eclipse.org/legal/epl-v10.html"}
  6. :dependencies [[org.clojure/clojure "1.6.0"]]
  7. :profiles {:dev {:plugins [[cider/cider-nrepl "0.7.0"]]}})

让我们启动一个 REPL 会话:


 
  1. M-x cider-jack-in

这将打开一个新的显示我们的 *cider-repl* 缓冲区的窗口。

首先需要做的事情就是添加一个简单的测试(实际上因为在默认情况下这是我们添加的唯一测试,我们第一次得到它)。打开 test 文件夹内的 core_test.clj 文件。使用以下代码替换它:


 
  1. (deftest pairs-of-values
  2. (let [args ["--server" "localhost"
  3. "--port" "8080"
  4. "--environment" "production"]]
  5. (is (= {:server "localhost"
  6. :port "8080"
  7. :environment "production"}
  8. (parse-args args)))))

我们只是分配一个参数列表,它们将从命令行传进 args 中,然后判断一个叫做 parse-args 的函数的返回值是否与那些命令行参数转换成的简单的 map 相等。

使用 C-c C-k 编译文件。我们应该会在 Emacs 窗口的底部得到一个错误信息,会提示 Clojure 找不到 parse-args。让我们尝试打开 core.clj 文件(C-x C-f)并加入如下定义来修复异常:


 
  1. (defn parse-args [args]
  2. {})

使用 C-c C-k 来编译它,保存它(C-x C-s),切换回测试缓冲区(C-x b 回车)并尝试再次编译(C-c C-k)。这时它会成功,所以尝试使用 C-c , 来运行它,你应该会得到一个显示了一些故障信息的测试报告缓冲区。要检查出现的问题,我们可以将我们的光标移动到红色的区域并按 C-c ‘ 键。下面显示了在 mini-buffer 中断言的错误:


 
  1. (not (= {:server "localhost",
  2. :port "8080",
  3. :environment "production"}
  4. {}))

总之,意思就是我们的 map 是空的,让我们来修复它:


 
  1. (defn parse-args [args]
  2. (apply hash-map args))

再次运行我们的测试会得到其他的错误:


 
  1. (not (= {:server "localhost",
  2. :port "8080",
  3. :environment "production"}
  4. {"--port" "8080",
  5. "--server" "localhost",
  6. "--environment" "production"}))

哎呀,我们的 key 仍然都是一些带破折号的字符串,我们需要去除它们并将他们转换成关键字:


 
  1. (defn parse-args [args]
  2. (into {} (map (fn [[k v]] [(keyword (.replace k "--" "")) v])
  3. (partition 2 args))))

在测试缓冲区中重新运行测试,大功告成。如果我们有多个测试文件,我们可以在 CLI 中使用以下命令来运行它们:


 
  1. $ lein test

重新从 Leiningen 中运行所有的测试可以成为开发功能或分支时打包前的一个很好的完整性检查,因为有一些情况下从一个 REPL 开发会导致被测试结果误导。举例来说,如果你删除了一个函数定义,但却仍然从其他函数中调用它,在你的进程重新启动之前你根本不会注意到这个问题。

这就是使用 Emacs 的 clojure-mode 和 cider-test 的一个非常简单的示例。

使用 REPL

还有一件我们没有发现的事就是在 Emacs 中具有一个运行中的 REPL 来帮助开发是多么有用。如果你仍然打开着你的项目,将窗口分割(C-x 2 (水平) 或 C-x 3 (垂直))为两个,以便你可以打开 core.clj 和 *cider-repl* 缓冲区。比方说,你正在编辑 core.clj 并想要运行你定义的函数。你决定要将 parse-args 中的匿名函数提取到一个叫做 keywordize 的函数中。

首先加载并使用 C-c C-k 在 REPL 缓冲区内编译。使用 C-c M-n 更改 REPL 缓冲区的命名空间为你的文件之一。现在使用 C-x o 命令切换到 REPL 窗口。

你现在可以访问已经在你编译文件时定义好的这个命名空间中的函数。试一下:


 
  1. command-line-args.core> (parse-args '("key" "value"))
  2. {:key "value"}

让我们继续在 core.clj 中创建我们的新函数:


 
  1. (defn keywordize [kvp]
  2. (let [[k v] kvp]
  3. [(keyword (.replace k "--" "")) v]))
  4. (defn parse-args [args]
  5. (into {} (map keywordize (partition 2 args))))

现在我们有几个选择,我们可以再次重新编译(C-c C-k)整个文件,或者我们可以使用 C-x C-e 让每个函数进行自我评估,其将会发送 s-exp 到运行中的 REPL。现在切换回 core.clj 的命名空间,并切换到 REPL 缓冲区,我们可以试用我们的 keywordize 函数:


 
  1. command-line-args.core> (keywordize ["--oh" "hai"])
  2. [:oh "hai"]

如果你的 REPL 开始变得混乱,使用 C-c M-o 来清除它是一个很好的办法。不断的变更代码的功能并运行它,是使 Emacs 与 lisp 很好的相结合来开发的事情之一。

如果你发现自己正想要重复刚刚在 REPL 中输入的命令,你可以使用 M-p 滚动历史命令并使用 M-n 向前(下一条)滚动。此外,所有的 Emacs 编辑命令都可以在 REPL 中使用,这真的很棒。

在 REPL 中可以使用一个很方便的函数 clojure.repl/doc,其可以让你查看指定函数的文档:


 
  1. command-line-args.core> (use 'clojure.repl)
  2. nil
  3. command-line-args.core> (doc println)
  4. -------------------------
  5. clojure.core/println
  6. ([& more])
  7. Same as print followed by (newline)
  8. nil

然而,当你的光标在一个函数名上的时候也可以使用快捷键 C-c C-d d 来查看。这将在一个新窗口中显示 Clojure (或 Javadoc) 的文档。相反,如果你想要跳转到函数的源码之中,你可以使用 M-. ,这真的很棒。这个方法既适用于你自己的函数又适用于那些来自第三方的库。使用 M-, 可以弹出堆栈并返回。对于一个文件中的所有被定义的东西来说,你都可以使用 M-x imenu 来显示它们并跳转到其中之一。

当你结束使用 REPL(或者由于某种原因它已经不能正常工作),你只需要键入 M-x cider-quit 关掉 *cider-repl* 缓冲区并重新运行 cider-jack-in 来启动另一个。

附录

MELPA 文档

可以通过 CIDER 文档 查阅 CIDER 快捷键。

贡献者

Gareth Jones, 2012 (原作者)

感谢 Phil HagelbergMikael Sundberg、和 Jake McCrary 在本指南被创建的原始博客文章中提的改善建议。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章