Clojure 程式設計/常見問題解答
作為一種函式式語言,Clojure 不鼓勵使用經典意義上的變數,即儲存可變值的儲存位置。如果一開始需要更多思考才能構建不需要變數的解決方案,請嘗試付出努力 - 它會讓你得到很多回報。例如
int i;
int sum;
for (i=1;i<=100;i++,sum+=i);
可以在 Clojure 中用不使用變數的方式編寫為
(apply + (range 1 100))
Clojure 確實支援變數,但您應該在使用它們之前完全閱讀有關它們的資訊,以瞭解它們的語義。如果您真的被困住,想要快速解決問題,可以使用 def
(def a 5)
(def a 6)
但不建議這樣做,僅建議將其用作解決方法,直到您能夠更深入地探索 Clojure。
Clojure 還支援變數作為不可變值名稱 - 意味著您不能在以後更改它。這些是透過將值繫結到名稱來建立的,並且始終具有可以使用該名稱的詞法範圍。函式引數就是一個這樣的例子,您應該從其他語言中熟悉它。
還有許多 Clojure 結構也建立臨時繫結(其中一些結構不止一次)。最簡單的是 let
(let [a 5, b 6] ; Comma is optional and treated as a whitespace
(+ a b))
(let [fact100 (factorial 100)]
(* fact100 fact100))
(zipmap [:a :b :c] [1 2 3])
;; ⇒ {:c 3, :b 2, :a 1}
假設我需要處理文字檔案中的行,並且偶爾(根據行內容)建立物件並將它新增到結果列表中。(例如,對文字檔案進行簡單解析)。例如,也許每 5 或 6 行會有一條空行,我用它來根據前面的行建立物件。由於行數不同,我不能使用 map 或任何函式式方法,至少看起來是這樣。是否需要使用 with-local-vars,或者有更優雅的方法?
以下程式碼搜尋一組行並返回匹配的行。def 行只是為我們提供了一個可以處理的集合。for 語句迭代 "lines" 並將每個條目分配給 "line"。對於每行,都會呼叫 :when 子句。如果返回 true,則執行 for 的主體,在本例中,只需返回 line。結果被放入一個序列中,與 map 函式相同,但只包含匹配的元素。
(def lines ["aa" "bb" "cc" "dd"])
(for [line lines :when (= "bb" line)] line)
;; ⇒ ("bb")
filter 函式可以實現類似的功能
(filter #{"bb"} lines)
;; ⇒ ("bb")
使用 Leiningen,要建立庫 foo
lein new foo
Generating a project called foo based on the 'default' template.
To see other templates (app, lein plugin, etc), try `lein help new`.
使用預設的 project.clj
(defproject foo "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.5.1"]])
注意,按照 Leiningen 的術語,"foo" 是工件的名稱,"0.1.0-SNAPSHOT" 是版本號。
轉到新建立的專案 foo 的根目錄。實現函式。安裝庫。
lein install
Created D:\yushen\dev\foo\target\foo-0.1.0-SNAPSHOT.jar
Wrote D:\yushen\dev\foo\pom.xml
Installed jar and pom into local repo.
上述內容適用於 Leiningen 2.4.2。
設定專案 try-foo
lein new try-foo
Generating a project called foo based on the 'default' template.
To see other templates (app, lein plugin, etc), try `lein help new`.
自定義 project.clj 檔案,以表示對庫 foo 的依賴關係
(defproject try-foo "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.5.1"]
[foo "0.1.0-SNAPSHOT"]])
然後開啟連線到專案的 repl
(use 'foo.core)
(foo "Hi")
庫應該可以使用了。
順序查詢不是一項重要的操作。Clojure 包含集合和對映,如果您要進行查詢,則應該使用它們。contains? 對映到 java.util.Set.contains。java.util.Collection 也包含 contains 的事實,在我看來,是一個錯誤,因為您實際上無法為具有極大差異效能特徵的介面編寫程式碼。因此,集合和對映中的查詢優先,並獲得最佳名稱 - contains?。人們使用其他語言中的 contains() 編寫幼稚的程式,並獲得相應的糟糕的 n 平方效能 - 這不是鼓勵 Clojure 中的論據。如果他們能解釋為什麼這是一個壞主意,以及如何使用集合和對映,那就沒問題了。
您可能在使用某些惰性序列的副作用時發現了一些意想不到的事情
(first (for [i (range 10)] (prn i)))
;; => 0 1 2 3 4 5 6 7 8 9
請參閱 De-chunkifying sequences in Clojure
對於將文件簡單地 GET 到字串中的情況,你可以簡單地將 URL 傳遞給 clojure.contrib.duck-streams/slurp*。
(use 'clojure.contrib.duck-streams)
(slurp* "http://www.example.org/")
;; ⇒ "<HTML>\r\n<HEAD>\r\n <TITLE>Example Web Page</TITLE>
對於更高階的用法,包括其他請求型別(如 POST),你可能會發現 clojure-http-client 庫很有用。最後,你當然可以直接在 Java URL 物件 上使用 (.openConnection)。
你可以使用 clojure-contrib "show"
(use '[clojure.contrib.repl-utils :only (show)])
(show Object)
;; ⇒
;; === public java.lang.Object ===
;; [ 0] <init> ()
;; [ 1] equals : boolean (Object)
;; [ 2] getClass : Class ()
;; [ 3] hashCode : int ()
;; [ 4] notify : void ()
;; [ 5] notifyAll : void ()
;; [ 6] toString : String ()
;; [ 7] wait : void ()
;; [ 8] wait : void (long)
;; [ 9] wait : void (long,int)
或者,如果你不想使用 "clojure.contrib",那麼以下方法應該可以完成部分工作
(map #(.getName %) (.getMethods Object))
;; ⇒ ("wait" "wait" "wait" "hashCode" "getClass" "equals" "toString" "notify" "notifyAll")
(doto (new java.util.HashMap) (.put "a" 1) (.put "b" 2))
;; ⇒ #<HashMap {b=2, a=1}>
(def hm (doto (new java.util.HashMap) (.put "a" 1) (.put "b" 2)))
hm
;; ⇒ #<HashMap {b=2, a=1}>
(def h (into {} hm))
h
;; ⇒ {"a" 1, "b" 2}
(h "a")
;; 1
Clojure 不是面向物件的(至少不是傳統意義上的)。Clojure 的核心前提之一是,在傳統的面向物件方式中混淆狀態和標識是一個壞主意,尤其是在面對併發的情況下。關於狀態和標識 以及 Rich Hickey 的 我們到那裡了嗎? 演講解釋了這種想法背後的部分原因。
另請參閱 Stuart Halloway 的 Rifle-Oriented Programming 文章,該文章解釋了 Clojure 對面向物件程式設計的關注點的一些答案。
當然,僅僅因為該語言不鼓勵這樣做並不意味著在 Clojure 中編寫面向物件程式碼是不可能的——在一定程度上,這可能是在與 Java 程式碼互動時所必需的。但是,傳統的 OO 樣式程式設計在 Clojure 中是非慣用的,並且不鼓勵這樣做。
確保你正在使用 paredit 版本 22(beta)
當你用 M-x slime 啟動它時,它會將 ~/.clojure 和 ~/.swank-clojure 下的所有 jar 檔案新增到你的類路徑中。你至少需要 clojure.jar、clojure-contrib.jar 和 swank-clojure.jar。你可以從 build.clojure.org 下載預先構建的 Clojure 和 Contrib jar 檔案,並從 Clojars 下載 swank-clojure.jar。
當你使用 M-x swank-clojure-project 並指定一個目錄時,swank-clojure 會將以下條目新增到類路徑中
- src/
- classes/
- lib/*.jar
你需要手動將你想要使用的 clojure、contrib 和 swank-clojure jar 版本新增到 lib 目錄中,或者使用 Leiningen 或 Maven 等依賴項獲取工具。
你可能缺少 swank-clojure.jar。請參閱前一個問題。
Clojure 目前針對 Java 5(以及更高版本)。
Clojure 1.0 和 1.1 都被許多人在不同版本的
- Sun 的 JRE 1.5.0 和 1.6.0
- OpenJDK 1.5.0 和 1.6.0
- IBM J9
上經常使用。它還已知可以在以下平臺上執行:
- 帶有 Classpath 的 JamVM