使用 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 在本指南被創建的原始博客文章中提的改善建議。

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