使用 Emacs 開發 Clojure

oschina發表於2015-12-24

本作品遵循 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 了:

$ brew install emacs --with-cocoa
$ brew linkapps Emacs

這應該會安裝 Emacs 的最新版本,並將 Emacs.app 符號連結到你的 ~/Applications 資料夾下。

編譯之後,Emacs 將會順利的存在於你的 cellar 中。你可以檢查一下:

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

如果你需要自定義你的環境的話(在 .profile 檔案或你的 shell-specific 配置中)你可以新增這個函式來解決當從 OS X 的 GUI 中啟動 Emacs 時的路徑問題(多虧了 Steve Purcell 將其提供在了 Clojure 郵件列表中):

;; fix the PATH variable
(defun set-exec-path-from-shell-PATH ()
  (let ((path-from-shell (shell-command-to-string "TERM=vt100 $SHELL -i -c 'echo $PATH'")))
    (setenv "PATH" path-from-shell)
    (setq exec-path (split-string path-from-shell path-separator))))

(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:

$ 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 從那裡尋找。

穩定版倉庫:

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

最新版倉庫:

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 移動到新的機器;你不必記得你手工安裝的所有東西。

(defvar my-packages '(better-defaults
                      projectile
                      clojure-mode
                      cider))

(dolist (p my-packages)
  (unless (package-installed-p p)
    (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 鍵。

考慮到這一點,以下列出你會經常使用到的基本按鍵:

檔案 / 緩衝區 / 視窗命令
C-x C-f     找到檔案
C-x C-s     儲存緩衝區
C-x s       儲存檔案 (類似另存為…)
C-x b       切換緩衝區
C-x k       關閉緩衝區
C-x 1       關閉其他視窗
C-x 0       關閉當前視窗
C-x 2       水平分割視窗
C-x 3       垂直分割視窗

移動命令
C-a         一行的開始
C-e         一行的結尾
C-n         後一行 (下)
C-p         前一行 (上)
C-b         後退 (左)
C-f         前進 (右)
M-f         以詞為單位前進
M-b         以詞為單位後退
C-v         向前翻頁
M-v         向後翻頁

修改命令
C-d         移除字元
M-d         移除詞
M-delete    移除前一個詞

其他命令
C-s         正規表示式向後搜尋
C-r         正規表示式向前搜尋
M-%         查詢替換

我還會提一下幫助命令:

C-h t     教程 (重溫基礎知識)
C-h b     描述所有當前繫結的快捷鍵
C-h m     描述當前模式
C-h a     Apropos - 搜尋幫助
C-h k     描述鍵

我建議至少讀一次教程,因為它會讓你對導航和移動命令有很好的理解。你還將大量使用的另一個命令就是 M-x,它允許你執行任何命令,而且真的非常多。Apropos 對於使用 C-h a 搜尋某個東西是非常有用的。

在讀完教程後(你沒有做到這一點,對不對?O_O)你可以移動游標、開啟檔案、儲存檔案等,通常可以基本適應了。學習 Emacs 是永無止境的,但這些基礎知識將永遠伴你左右。

建立一個專案

讓我們通過建立一個小的 Clojure 示例專案的過程,來演示 Emacs 如何幫我們成為 Lisp 沃土的捍衛者。

我們要建立的專案是一個平常的使用傳給它的引數來把它們轉換成 map 中的鍵值對的簡單命令列解析器。功能無關緊要,而且也沒啥用。其目的只是為了演示開發流程。

如果你還沒有 Leiningen,就安裝它並使用它建立一個新的專案:

$ lein new command-line-args
$ cd command-line-args

摟一眼專案的結構:

+ doc
  - intro.md
- project.clj
- README.md
+ src
  + command_line_args
    - core.clj
+ test
  + command_line_args
    - core_test.clj

應該不用過多解釋了,雖然 Leiningen 內建的教程(通過 lein help tutorial 命令檢視)提供了對專案結構的詳細解釋。

在我們繼續之前,我們必須使用 CIDER 來開發我們的專案,這需要進行一點一次性的設定。開啟 project.clj 檔案並在此新增 cider-nrepl 外掛。該檔案最初應該看起來像這樣(或稍有不同):

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

你需要這樣做(請記住,你現在讀到本文時 cider-nrepl 可能已經有了更新的版本):

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

讓我們啟動一個 REPL 會話:

M-x cider-jack-in

這將開啟一個新的顯示我們的 *cider-repl* 緩衝區的視窗。

首先需要做的事情就是新增一個簡單的測試(實際上因為在預設情況下這是我們新增的唯一測試,我們第一次得到它)。開啟 test 資料夾內的 core_test.clj 檔案。使用以下程式碼替換它:

(deftest pairs-of-values
   (let [args ["--server" "localhost"
               "--port" "8080"
               "--environment" "production"]]
      (is (= {:server "localhost"
              :port "8080"
              :environment "production"}
             (parse-args args)))))

我們只是分配一個引數列表,它們將從命令列傳進 args 中,然後判斷一個叫做 parse-args 的函式的返回值是否與那些命令列引數轉換成的簡單的 map 相等。

使用 C-c C-k 編譯檔案。我們應該會在 Emacs 視窗的底部得到一個錯誤資訊,會提示 Clojure 找不到 parse-args。讓我們嘗試開啟 core.clj 檔案(C-x C-f)並加入如下定義來修復異常:

(defn parse-args [args]
  {})

使用 C-c C-k 來編譯它,儲存它(C-x C-s),切換回測試緩衝區(C-x b 回車)並嘗試再次編譯(C-c C-k)。這時它會成功,所以嘗試使用 C-c , 來執行它,你應該會得到一個顯示了一些故障資訊的測試報告緩衝區。要檢查出現的問題,我們可以將我們的游標移動到紅色的區域並按 C-c ‘ 鍵。下面顯示了在 mini-buffer 中斷言的錯誤:

(not (= {:server "localhost",
         :port "8080",
         :environment "production"}
        {}))

總之,意思就是我們的 map 是空的,讓我們來修復它:

(defn parse-args [args]
  (apply hash-map args))

再次執行我們的測試會得到其他的錯誤:

(not (= {:server "localhost",
         :port "8080",
         :environment "production"}
        {"--port" "8080",
         "--server" "localhost",
         "--environment" "production"}))

哎呀,我們的 key 仍然都是一些帶破折號的字串,我們需要去除它們並將他們轉換成關鍵字:

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

在測試緩衝區中重新執行測試,大功告成。如果我們有多個測試檔案,我們可以在 CLI 中使用以下命令來執行它們:

$ 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 視窗。

你現在可以訪問已經在你編譯檔案時定義好的這個名稱空間中的函式。試一下:

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

讓我們繼續在 core.clj 中建立我們的新函式:

(defn keywordize [kvp]
  (let [[k v] kvp]
    [(keyword (.replace k "--" "")) v]))

(defn parse-args [args]
  (into {} (map keywordize (partition 2 args))))

現在我們有幾個選擇,我們可以再次重新編譯(C-c C-k)整個檔案,或者我們可以使用 C-x C-e 讓每個函式進行自我評估,其將會傳送 s-exp 到執行中的 REPL。現在切換回 core.clj 的名稱空間,並切換到 REPL 緩衝區,我們可以試用我們的 keywordize 函式:

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

如果你的 REPL 開始變得混亂,使用 C-c M-o 來清除它是一個很好的辦法。不斷的變更程式碼的功能並執行它,是使 Emacs 與 lisp 很好的相結合來開發的事情之一。

如果你發現自己正想要重複剛剛在 REPL 中輸入的命令,你可以使用 M-p 滾動歷史命令並使用 M-n 向前(下一條)滾動。此外,所有的 Emacs 編輯命令都可以在 REPL 中使用,這真的很棒。

在 REPL 中可以使用一個很方便的函式 clojure.repl/doc,其可以讓你檢視指定函式的文件:

command-line-args.core> (use 'clojure.repl)
nil
command-line-args.core> (doc println)
-------------------------
clojure.core/println
([& more])
  Same as print followed by (newline)
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 在本指南被建立的原始部落格文章中提的改善建議。

相關文章