Clojure 程式設計/教程和技巧
- 官方 Clojure 參考和 API : 連結
- R. Mark Volkmann 的 "Clojure - JVM 的函數語言程式設計" : 連結
- Moxley Stratton - "面向非 Lisp 程式設計師的 Clojure 教程" : 連結
- Satish Talim - "Clojure 筆記" : 連結
- Eric Rochester - "Clojure 系列"(處理標記和詞幹提取) : 連結
- "面向 ImageJ 的 Clojure 教程" : 連結
- "Wikibooks.org/Clojure 程式設計/概念" : 連結
- Peter Seibel - "實用 Common Lisp" => 原文 ; 移植到 clojure
- Paul Graham - "On Lisp" => 原文 ; fogus 翻譯了前 5 章 連結 ,Halloway 翻譯了第 7、9、10 章 連結 ,pangloss 翻譯了第 2 章到第 16 章的大部分內容 在 github 上
- "計算機程式的結構和解釋" => 原文 ; 第 1 章移植到 clojure 連結
- "Clojure、Emacs 和 Slime/Swank 在 Ubuntu 8.10 上" : 連結
- Clojure 簡明入門指南(上次更新: 2012-03)
Rich Hickey 做了一場名為 "面向 Java 程式設計師的介紹" 的演講。音訊和幻燈片在 Clojure blip.tv 頻道 上分兩部分提供。很好的 "Clojure 指令碼" 教程 這裡 涵蓋了 Clojure 的許多基礎知識,以及它的 Java 整合(例如,從 Clojure 使用 ImageJ)。
主站點的 Java 互動參考 展示瞭如何從 Clojure 呼叫 Java 程式碼。但由於 Clojure 是作為 Java 類庫實現的,因此也可以輕鬆地將 Clojure 嵌入 Java 應用程式中,載入程式碼並呼叫函式。
主站點的 Java 互動參考 還展示瞭如何使用 Clojure 1.6 中引入的 clojure.java.api 包 來實現這一點。
當被問及是否使用 SICP 來學習 Clojure 時,Rich 說:[2008 年 3 月 26 日]
- 當然,每個人的經歷都不一樣。這是我的看法
- 我認為 SICP 不是關於程式語言的書。它是一本關於程式設計的書。它使用 Scheme 因為它在許多方面是一種原子程式語言。Lambda 演算 + 尾呼叫最佳化 (TCO) 用於迴圈 + 延續用於控制抽象 + 語法抽象 (宏) + 可變狀態用於你需要的時候。它非常小。它已經足夠了。
- 這本書真正討論的是程式設計中的問題。模組化、抽象、狀態、資料結構、併發性等等。它提供了通用分派、物件、併發、惰性列表、(可變) 資料結構、"標記" 等的描述和玩具實現,旨在闡明問題。
- Clojure 不是一種原子程式語言。我太累/老/懶了,不想用原子程式設計。Clojure 提供了通用分派、關聯對映、元資料、併發基礎設施、持久資料結構、惰性序列、多型庫等的生產實現。Clojure 已經提供了比你按照 SICP 中的步驟構建的某些事物更好的實現。
- 因此,SICP 的價值在於幫助你理解程式設計概念。如果你已經理解了這些概念,Clojure 讓你能夠更快地編寫有趣且健壯的程式,IMO。我認為 Clojure 的核心並不比 Scheme 的核心大多少。Schemers 怎麼看?
- 我認為在 Clojure 之前,Lisp 語言在函數語言程式設計和列表方面引領了你走上了一條很好的道路,但當你需要編寫真正程式時,它卻讓你陷入困境,因為這些資料結構(如果提供的話)是可變的且命令式的。早期的 Lisp 也是在普遍的程序內併發出現之前,以及在高效能多型分派(例如虛擬函式)作為庫基礎設施的價值得到充分理解之前設計的。它們的庫的多型性明顯有限。
- Stuart Halloway 的著作《Programming Clojure》現已作為電子書(測試版)釋出,紙質版也將在不久後推出。當然,在 Scheme 語言中,除了標準之外,還提供更多完整的功能(大多數 Scheme 語言都是如此),但也沒有關於這方面的書籍。兩種情況下都只有文件。
- 在學習 Clojure 的過程中學習 Scheme 或 Common Lisp 語言是不錯的選擇。會有一些細節無法直接轉換(從 Scheme 語言 - 沒有 TCO、false/ nil/() 的差異、沒有延續;從 CL 語言 - Lisp-1、符號/變數二分法)。但就我個人而言,我認為 SICP 不會對學習 Clojure 有太大幫助。YMMV。
| 函式型別 | Scheme 函式 | Clojure 函式 |
|---|---|---|
| 列表 | cons | cons |
| list? | seq? | |
| car | first | |
| cdr | rest | |
| caar, cadr, ... | ffirst, fnext, ... |
Clojure for Common Lisp Programmers
[edit | edit source]下表列出了一些 Common Lisp 實體(函式、宏等)及其(大致)等效的 Clojure 實體。請注意,某些概念可能不是完全匹配。要了解 Clojure 實體的精確行為,建議讀者參考 Clojure 主頁提供的 Clojure 參考文件。請注意,這些差異中的一些可能是因為 Clojure 的淵源來自 Lisp-1,而不是 Lisp-2。
Clojure 參考文件還記錄了與 Lisp 語言的常見差異,點選此處檢視。
| Common Lisp 功能 | Clojure 等效項 |
|---|---|
| load | load-file |
| make-array 或 #(N N N N) | vector 或 [N N N N] |
| #| |#(多行註釋) | (comment ...) |
| documentation | doc, find-doc, javadoc |
| in-package | in-ns |
| defstruct | defstruct 和 create-struct(在 1.3 版本中建議使用 defrecord 或 deftype) |
| defun | defn |
| inline | definline |
| lambda | fn 或 閱讀器宏 #(..) |
| cons | cons, lazy-cons, conj |
| car, first | first |
| cdr, rest | rest |
| #\x(字元) | \x |
| , | ~ |
| ,@ | ~@ |
| eq(及其變體) | = |
| expt | Math/pow java.lang.Mathuser=> (Math/pow 10 2) 100.0 |
| format | String/format java.lang.Stringuser=> (format "0x%x 0x%x 0x%x" 10 20 30) "0xa 0x14 0x1e" or user=> (clojure.pprint/cl-format false "~R" 42) "forty-two" |
| gensym | gensym 或 在符號名稱後加 #,因為 ` 可以自動生成符號。user=> `(x#) (x__2136) |
| progn | do |
| type-of | class |
| typep | instance? |
| let | let 和 繫結 |
| do | loop + recur(loop [temp-one 1
temp-two 0]
(if (= 3 temp-two)
temp-one
(recur (inc temp-one) (inc temp-one))))
=> 3
|
| cond | cond,去掉一層括號(cond test-form1
form1
test-form2
form2
...
:else ;; :else or non nil or true
form)
|
| Hyperspec |
http://clojure.github.com/clojure/ |
Clojure for Python/Ruby/Perl Programmers
[edit | edit source]Ruby 中 Enumerable 和 Array 類所有方法的等效 Clojure 函式 列表。
Clojure 中的單元測試
[edit | edit source]Clojure 提供了多種單元測試解決方案。它們對測試方法有略微不同的理念。可以嘗試使用每個解決方案,看看哪一個最符合你的測試理念。
test-is
[edit | edit source]Stuart Sierra 的 test-is 框架包含在 clojure.contrib 中。它允許你使用 "is" 宏標記函式定義,並在其中宣告斷言,如以下示例所示(來自程式碼)
(defn add2
([x] (+ x 2))
{:test (fn [] (is (= (add2 3) 5))
(is (= (add2 -4) -2)
(is (> (add2 50) 50)))}
測試也可以單獨構建
(deftest test-new-fn (is (= (new-fn) "Awesome")))
想要了解更多資訊,最好檢視原始碼本身,它位於 Google Code 上的 clojure-contrib 庫中。
在最近的版本中,此函式已移至核心發行版中的 clojure.test 名稱空間;clojure-contrib 中仍然存在一個相容性庫。
Fact
[edit | edit source]Fact 是 James Reeves 編寫的單元測試庫,其風格類似於 Ruby 中的 RSpec。使用這種方法,你可以將測試編寫為“事實”,每個事實都有一個斷言來證明該事實。
(fact "The length of a concatenated list is equal to the length of its parts"
[xs (rand-seqs rand-ints)
ys (rand-seqs rand-ints)]
(= (count (concat xs ys))
(+ (count xs) (count ys))))
James 在 Google 集團中釋出了關於此庫的描述,點選此處檢視。此庫託管在 github 上,點選此處檢視。
unit-test
[edit | edit source]unit-test 是一個 xUnit 風格的單元測試系統,它允許你使用 deftest 宏定義測試。測試包含各種斷言,如果其中一個斷言失敗,則測試失敗。
示例
(deftest my-example-test []
(let [x 1]
(assert-equal 1 x "x is NOT one!")))
unit-test 的原始版本尚未隨著 Clojure 的更改而更新(原始主頁點選此處),但 Tyler McMullen 已對其進行了修補,並在 github 上釋出了一個工作版本,點選此處檢視。
Clojure 中的 Shebang 指令碼
[edit | edit source]此方法只在 Linux 上測試過,但在其他 Un*x 系統上也應該可以執行。
將以下內容放入 command-line-args.clj 檔案中
#^:shebang '[ exec java -cp "$HOME/.m2/repository/org/clojure/clojure/1.5.1/clojure-1.5.1.jar" clojure.main "$0" "$@" ] (prn *command-line-args*)
使用以下命令將其設為可執行檔案
$ chmod 755 command-line-args.clj
然後使用引數執行它。
$ ~/src/clj/lab/command-line-args.clj a b c
("a" "b" "c")
這種方法的解釋在 Clojure 集團的 這封郵件中進行了描述。
此方法的更現代版本是將 command-line-args.clj 編寫為
":";exec java -cp "$HOME/path-to-clojure.jar" clojure.main $0 "$@" (ns command-line-args) (defn command-line? [] (.isAbsolute (java.io.File. *file*))) (defn main [] (println *command-line-args*)) (if (command-line?) (main))
它具有更簡單的“shebang”行。
(main) 在從命令列呼叫指令碼時始終執行,即使在沒有引數的情況下呼叫時也是如此。
當 command-line-args 被另一個 Clojure 檔案使用或需要時,(main) 不會執行。
此方法的靈感來自 這種 Emacs 指令碼方法,並且出於相同的原因有效。
Clojure 的最近更新將 #! 設為從行尾到行尾的註釋。使用修訂版 1106 或更高版本,如果你建立了 clj 指令碼(例如在 入門中描述的那些指令碼),則可以像往常一樣進行 shebang 指令碼編寫。
#! /usr/bin/env clj (println "Hello World!")
否則,你可以直接引用 java jar 檔案,如下所示
#! /usr/bin/java -jar clojure.jar clojure.lang.Script
注意:此方法可能不適用於所有系統,因為在某些系統上只允許使用一個引數!
在 Windows 上,類似的方法適用於將 Clojure 指令碼嵌入到批處理檔案中
:x (comment @echo off java -cp clojure.jar clojure.main "%~f0" %* goto :eof ) (println "Hi!" *command-line-args*)
在此,第一行被 cmd.exe 看作是一個標籤(因為開頭是冒號),被 Clojure 看作是一個裸關鍵字,後面跟著一個多行註釋的開始。下一行(到閉合括號)執行 Java,並指定適當的類路徑,傳遞批處理檔名 ("%~f0") 和命令列引數 ("%*"),然後退出批處理檔案(goto eof)。閉合括號終止 Clojure 註釋,Clojure 解釋檔案中的其餘部分。
將應用程式以獨立的 .jar 檔案形式分發
[edit | edit source](在 bash shell 下的 linux 系統上完成)
- 將 ./classes 和 ./ 新增到 CLASSPATH shell 變數中,並確保 Clojure 也使用此類路徑(可能意味著編輯 clj bash 指令碼)
bash# export CLASSPATH=./classes:./
- bash# mkdir -p project/{app,classes}
- bash# cd project
- 使用以下內容建立 Clojure 應用程式 app/hello.clj
(ns app.hello (:gen-class)) (refer 'clojure.core) ; not sure if this is necessary (defn -main [& args] (println "application works"))
- 使用 Clojure REPL/shell 編譯 Clojure 應用程式
user=> (compile 'app.hello) app.hello user=> <CTRL><d>
- 已編譯的應用程式位於 classes/app 中
bash# ls classes/app/ hello.class hello__init.class hello$_main__4.class
- 將 clojure.jar 解壓縮到 ./classes 中
bash# unzip /opt/clojure/clojure.jar -d classes/.
- 刪除 ./classes/META-INF
bash# rm -r ./classes/META-INF
- 建立一個名為 mf-app.txt 的清單文字檔案,內容如下
Main-Class: app.hello Class-Path: .
(確保檔案在 "." 字元後以換行符結尾)
- 建立 .jar 檔案
bash# jar cmf mf-app.txt app.jar -C classes .
(命令以 "." 結尾)
- 嘗試執行應用程式(app.jar)
bash# java -jar app.jar application works
使用 (ns) 宏
[edit | edit source]使用 (ns) 宏可能會有點棘手。以下是一些可能有所幫助的示例。
- 需要 clojure.contrib.str-utils 中的所有函式,但不要直接匯入它們到名稱空間中。
(ns foo (:require clojure.contrib.str-utils)) (clojure.contrib.str-utils/str-join ", " ["foo" "bar"])
- 將 clojure.contrib.str-utils 中的所有函式直接匯入到名稱空間中。
(ns foo (:use clojure.contrib.str-utils)) (str-join ", " ["foo" "bar"])
- 使用別名名稱空間匯入 clojure.contrib.str-utils 中的所有函式。
(ns foo (:require [clojure.contrib [str-utils :as str-utils]])) (str-utils/str-join "," ["foo" "bar"])
- 排除 Clojure 的“list”函式,以便你可以定義自己的同名函式。
(ns foo (:refer-clojure :exclude [list]))