Clojure

REPL 程式設計:基本用法

評估 Clojure 表達式

啟動 REPL(如 前一章節 所述),現在您可以透過在 REPL 中輸入 Clojure 表達式並按下 ENTER 來評估它們

user=> (+ 2 3)
5
user=> (defn factorial [n]
(if (= n 0)
  1
  (* n (factorial (dec n)))))
#'user/factorial
user=> (factorial 10)
3628800
user=>

在每個表達式下方,我們會看到評估表達式的結果。這就是 REPL 的作用:對於我們提交給它的每個表達式,REPL 會Read(讀取)它,Evaluate(評估)它,然後Print(列印)結果,所有這些都在一個Loop(迴圈)中進行。

如果您正在學習 Clojure,請花點時間在 REPL 中進行實驗。它提供的快速回饋迴圈可形成非常有效的學習環境。

儘管上述範例非常基本,但你可以用這種方式執行功能齊全的 Clojure 程式。Clojure 的設計讓它的 REPL 環境提供語言的全部功能:你可以透過將來源檔案的內容按正確順序貼到 REPL 中,實際執行任何現有的 Clojure 程式。

提示:在 REPL 旁邊使用編輯器

在終端機視窗內編輯 Clojure 程式碼可能會很繁瑣;遇到這種情況時,一個簡單的技巧是在你選擇的文字編輯器中撰寫程式碼,該編輯器具備支援 Clojure 語法的模式,然後將程式碼從編輯器複製貼上到 REPL 終端機視窗。以下是範例說明(使用的編輯器為 Atom

Editor next to CLI REPL

在這個指南的提升你的 REPL 工作流程章節中,我們將看到更符合人體工學的 REPL 使用設定。不過,這個極簡主義設定已足夠應付本教學課程的範圍,而且對於精通基礎知識非常重要。

列印的兩種方式

考慮以下評估

user=> (println "Hello World")
Hello World
nil

這很奇怪:與前述範例不同,看起來評估 (println "Hello World") 表達式產生了 2 個結果:Hello Worldnil

這是因為 println 函式會將其引數列印到標準輸出,但傳回 nil。因此,我們在表達式下方看到的 2 行在性質上非常不同

  • Hello World 是評估表達式(列印到標準輸出)的副作用:列印是由我們的程式碼完成的。

  • nil 是評估表達式的結果:列印是由 REPL 完成的。

從 REPL 呼叫 Clojure 函式庫

到目前為止,我們只呼叫我們在 REPL 中手動定義的程式碼(例如我們上面定義的 factorial 函式)。但 REPL 也讓你使用預先存在的 Clojure 程式碼,也就是 Clojure 函式庫[1] 給定命名空間為 my.name.space 的 Clojure 函式庫,你可以評估 (require '[my.name.space]) 以載入該函式庫的程式碼,並在 REPL 中使用。

範例:使用 clojure.string

例如,clojure.string 是 Clojure 中用於處理文字的函式庫。讓我們載入 clojure.string 並呼叫其 clojure.string/upper-case 函式

user=> (require '[clojure.string])
nil
user=> (clojure.string/upper-case "clojure")
"CLOJURE"

require 也讓我們可以為 clojure.string 名稱空間定義一個 別名,方法是加入 :as 子句。這讓我們可以更簡潔地參照 clojure.string 名稱空間中定義的名稱

user=> (require '[clojure.string :as str])
nil
user=> (str/upper-case "clojure")
"CLOJURE"

最後,如果我們 非常 懶惰,完全不想輸入別名,我們可以加入 :refer 子句

user=> (require '[clojure.string :refer [upper-case]])
nil
user=> (upper-case "clojure")
"CLOJURE"

查詢文件

REPL 也可用於查詢 API 文件,方法是使用 clojure.repl 函式庫。在 REPL 中評估下列表達式

user=> (require '[clojure.repl :refer :all])
nil

此表達式讓 clojure.repl 名稱空間中定義的所有名稱在 REPL 中可用。

文件

你可以透過評估 (doc MY-VAR-NAME) 來列印給定 Var 的 API 文件

user=> (doc nil?)
-------------------------
clojure.core/nil?
([x])
  Returns true if x is nil, false otherwise.
nil
user=> (doc clojure.string/upper-case)
-------------------------
clojure.string/upper-case
([s])
  Converts string to all upper-case.
nil

來源

你也可以使用 source 來檢視用於定義 Var 的原始碼

user=> (source some?)
(defn some?
  "Returns true if x is not nil, false otherwise."
  {:tag Boolean
   :added "1.6"
   :static true}
  [x] (not (nil? x)))
nil

目錄

你可以使用 dir 來列出給定名稱空間中定義的所有 Var 的名稱。讓我們使用 clojure.string 名稱空間來執行此操作

user=> (dir clojure.string)
blank?
capitalize
ends-with?
escape
includes?
index-of
join
last-index-of
lower-case
re-quote-replacement
replace
replace-first
reverse
split
split-lines
starts-with?
trim
trim-newline
triml
trimr
upper-case
nil

另一個範例,讓我們使用 dir 來查看 clojure.repl 本身有哪些可用功能

user=> (dir clojure.repl)
apropos
demunge
dir
dir-fn
doc
find-doc
pst
root-cause
set-break-handler!
source
source-fn
stack-element-str
thread-stopper
nil

我們認得我們到目前為止使用過的 docsourcedir 操作。

相關

如果你不完全記得某些 Var 的名稱,你可以使用 apropos 來搜尋

user=> (apropos "index")
(clojure.core/indexed? clojure.core/keep-indexed clojure.core/map-indexed clojure.string/index-of clojure.string/last-index-of)

apropos 只會搜尋 Var 名稱;你可以使用 find-doc 來搜尋文件字串(由 doc 列印的文字)

尋找文件

user=> (find-doc "indexed")
-------------------------
clojure.core/contains?
([coll key])
 Returns true if key is present in the given collection, otherwise
 returns false.  Note that for numerically indexed collections like
 vectors and Java arrays, this tests if the numeric key is within the
 range of indexes. 'contains?' operates constant or logarithmic time;
 it will not perform a linear search for a value.  See also 'some'.
-------------------------
clojure.core/indexed?
([coll])
 Return true if coll implements Indexed, indicating efficient lookup by index
-------------------------
clojure.core/keep-indexed
([f] [f coll])
 Returns a lazy sequence of the non-nil results of (f index item). Note,
 this means false return values will be included.  f must be free of
 side-effects.  Returns a stateful transducer when no collection is
 provided.
-------------------------
clojure.core/map-indexed
([f] [f coll])
 Returns a lazy sequence consisting of the result of applying f to 0
 and the first item of coll, followed by applying f to 1 and the second
 item in coll, etc, until coll is exhausted. Thus function f should
 accept 2 arguments, index and item. Returns a stateful transducer when
 no collection is provided.
nil

文件僅提供給已載入的函式庫。

例如,如果你尚未載入 clojure.set 名稱空間,你將無法搜尋 clojure.set/union 的文件。此範例 REPL 會話說明了這一點

clj
Clojure 1.10.0
user=> (doc clojure.set/union)
nil                             ;; no doc found
user=> (apropos "union")
()
user=> (require '[clojure.set]) ;; now we're requiring clojure.set
nil
user=> (doc clojure.set/union)
-------------------------
clojure.set/union
([] [s1] [s1 s2] [s1 s2 & sets])
  Return a set that is the union of the input sets
nil
user=> (apropos "union")
(clojure.set/union)
user=>

1. 請注意,我們稱呼 Clojure 函式庫 的東西,不一定是一個 函式庫:它也可以是您目前專案中的原始碼檔案。