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 环境提供了语言的全部功能:您实际上可以运行任何现有的 Clojure 程序,只需将源文件的内容按正确的顺序粘贴到 REPL 中即可。

提示:在 REPL 旁边使用编辑器

在终端窗口中编辑 Clojure 代码可能会很繁琐;在这种情况下,一个简单的技巧是在您选择的具有语法感知 Clojure 模式的文本编辑器中编写代码,并将代码从编辑器复制粘贴到 REPL 终端窗口中。以下是一个示例(使用的编辑器是Atom)。

Editor next to CLI REPL

在本指南的增强您的 REPL 工作流程一章中,我们将看到使用 REPL 的更符合人体工程学的配置。但是,这种极简的设置足以满足本教程的范围,并且对于掌握基础知识很重要。

两种打印方式

考虑以下评估

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

这很奇怪:与之前的示例不同,它看起来像评估 (println "Hello World") 表达式产生了两个结果:Hello Worldnil

这是因为 println 函数会将它的参数打印到标准输出,但会返回 nil。因此,我们在表达式下看到的两行本质上是不同的

  • 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

您可以通过评估 (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

您还可以使用 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

您可以使用 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 操作。

apropos

如果您不记得某个 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 打印的文本)。

find-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 不一定是一个 :它也可以是您当前项目中的源代码文件。