跳轉到內容

Clojure 程式設計/常見問題解答

來自 Wikibooks,開放世界中的開放書籍

您希望在常見問題解答中看到什麼?

[編輯 | 編輯原始碼]

基礎知識

[編輯 | 編輯原始碼]

如何宣告變數?

[編輯 | 編輯原始碼]

作為一種函式式語言,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")

如何在本地建立 Clojure 庫並將其與 Leiningen 一起使用?

[編輯 | 編輯原始碼]

建立 Clojure 庫

[編輯 | 編輯原始碼]

使用 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")

庫應該可以使用了。

為什麼 contains? 在向量和列表上沒有按照我的預期執行

[編輯 | 編輯原始碼]

順序查詢不是一項重要的操作。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

如何獲取網頁併發出 HTTP 請求?

[編輯 | 編輯原始碼]

對於將文件簡單地 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)。

Java 互動

[編輯 | 編輯原始碼]

如何查詢 Java 物件包含哪些方法

[編輯 | 編輯原始碼]

你可以使用 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")

如何將 Java HashMap 轉換為 Clojure 對映?

[編輯 | 編輯原始碼]
 (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 是面向物件的語言嗎?

[編輯 | 編輯原始碼]

Clojure 不是面向物件的(至少不是傳統意義上的)。Clojure 的核心前提之一是,在傳統的面向物件方式中混淆狀態和標識是一個壞主意,尤其是在面對併發的情況下。關於狀態和標識 以及 Rich Hickey 的 我們到那裡了嗎? 演講解釋了這種想法背後的部分原因。

另請參閱 Stuart Halloway 的 Rifle-Oriented Programming 文章,該文章解釋了 Clojure 對面向物件程式設計的關注點的一些答案。

當然,僅僅因為該語言不鼓勵這樣做並不意味著在 Clojure 中編寫面向物件程式碼是不可能的——在一定程度上,這可能是在與 Java 程式碼互動時所必需的。但是,傳統的 OO 樣式程式設計在 Clojure 中是非慣用的,並且不鼓勵這樣做。

Emacs 和 Clojure

[編輯 | 編輯原始碼]

我已經安裝了 paredit.el,但它不適用於花括號! {}

[編輯 | 編輯原始碼]

確保你正在使用 paredit 版本 22(beta)

swank-clojure(SLIME)在哪裡查詢 Clojure 的 jar 檔案?

[編輯 | 編輯原始碼]

當你用 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 等依賴項獲取工具。

Slime 停滯在訊息:輪詢 "/tmp/slime.####".. (使用 `M-x slime-abort-connection' 中止。)

[編輯 | 編輯原始碼]

你可能缺少 swank-clojure.jar。請參閱前一個問題。

JVM 和 Clojure

[編輯 | 編輯原始碼]

支援哪些版本的 Java?

[編輯 | 編輯原始碼]

Clojure 目前針對 Java 5(以及更高版本)。

測試過哪些版本的 Java?

[編輯 | 編輯原始碼]

Clojure 1.0 和 1.1 都被許多人在不同版本的

  • Sun 的 JRE 1.5.0 和 1.6.0
  • OpenJDK 1.5.0 和 1.6.0
  • IBM J9

上經常使用。它還已知可以在以下平臺上執行:

  • 帶有 Classpath 的 JamVM
華夏公益教科書