summaryrefslogtreecommitdiff
path: root/content
diff options
context:
space:
mode:
Diffstat (limited to 'content')
-rw-r--r--content/article/tutorial-clj-0.md18
1 files changed, 12 insertions, 6 deletions
diff --git a/content/article/tutorial-clj-0.md b/content/article/tutorial-clj-0.md
index af5c02c1..444ba2aa 100644
--- a/content/article/tutorial-clj-0.md
+++ b/content/article/tutorial-clj-0.md
@@ -138,9 +138,14 @@ Array("a", "b", "c")
得到的是字符串本身!为什么会这样?
-在 Clojure(以及所有的 Lisp)里,数据可以大致理解为两类:原子(atom)和形式(form)。括号里不能再分的东西就是原子,比如 `list` 和 `"a"` 都是原子,而 `(list "a" "b" "c")` 是形式。
+在 Clojure(以及所有的 Lisp)里,数据可以大致理解为两类:原子(atom)和列表(list)。括号里不能再分的东西就是原子,比如 `list` 和 `"a"`,而 `(list "a" "b" "c")` 是列表。
-对原子执行 `eval` 操作,或者说求值(evaluate),得到的是原子本身,比如 `(eval 1)` 得到的就是 `1`;对形式求值,就要把形式的第一个原子当作操作,剩下的原子当作参数——这就是我们在前一节建立的对 Clojure 的理解。
+对原子执行 `eval` 操作,或者说求值(evaluate),得到的是原子本身,比如 `(eval 1)` 得到的就是 `1`(因此原子更专业的名字是 *[Self-evaluating objects](https://www.lispworks.com/documentation/HyperSpec/Body/26_glo_s.htm#self-evaluating_object)*,自求值对象);对列表求值,意思就是把列表当作形式(form)而非数据对象(object),形式的第一个元素是操作符(operator),剩下的是参数(arguments)——这就是我们在前一节建立的对 Clojure 的理解。
+
+1. 原子(atom):不是列表、不可再分的数据对象(object)。
+2. 列表(list):包裹在括号内的一个或多个原子。[^2]
+3. 对象(object):不被求值的原子或列表。
+4. 形式(form):被求值的列表,第一个元素是操作符,剩下的元素是参数(被求值的原子是自求值对象,求值的结果是它本身)
其实我们一直预设我们写的所有 Clojure 代码最终都会被求值。如果把一整个 Clojure 源代码文件当作形式(form)的集合,我们执行源代码的过程其实就是在对其中的形式求值(eval)。
@@ -153,14 +158,14 @@ Array("a", "b", "c")
;; => "(list \"a\" \"b\" \"c\")"
```
-`eval` 求值的是一个原子,一个字符串值,不是形式。
+`eval` 求值的是一个字符串值,字符串是原子,是自求值对象,所以求值的结果是它本身。
```clojure
(eval '(list "a" "b" "c"))
;; => ("a" "b" "c")
```
-这段代码才是在对形式求值。是时候解释 `'` 是什么了。这个符号其实是个语法糖,上面的代码等价于:
+这段代码才是把列表当作形式来求值。是时候解释 `'` 是什么了。这个符号其实是个语法糖,上面的代码等价于:
```clojure
(eval (quote (list "a" "b" "c")))
@@ -183,9 +188,9 @@ Array("a", "b", "c")
;; class java.lang.String cannot be cast to class clojure.lang.IFn (java.lang.String is in module java.base of loader 'bootstrap'; clojure.lang.IFn is in unnamed module of loader 'app')
```
-Clojure 没办法把字符串 `"a"` 转换为函数类型的数据,所以报错了。
+于是 `("a" "b" "c")` 这个列表被当作形式,`"a"` 被当作操作符,而 Clojure 没办法把字符串 `"a"` 转换为操作符,所以报错了。
-要保证 Clojure 不把这个列表误以为是函数调用,就要用到 `quote`。给形式打上引号,让 Clojure 把 `quote` 里面的东西当作数据而不是代码来处理。
+要保证 Clojure 不把这个列表误以为是函数调用,就要用到 `quote`。给形式打上引号,让 Clojure 把 `quote` 里面的东西当作对象而不是形式来处理。
写在源代码文件里的形式最终都会在运行时被求值(eval),如果想要一部分形式不被求值、被当作数据处理,就要打引号,用 `quote` 或者 `'` 标记。`quote` 看起来像函数,实际上是个特殊形式(special form),里面的参数不会被求值。
@@ -251,3 +256,4 @@ Clojure 代码在运行时默认被求值**一次**,若是多次求值,含ä¹
[^1]: 虽然大体一致,底层的逻辑其实是不同的,`apply` 并不是 `(eval (cons func args))` 的语法糖。不过,`eval` 和 `apply` 都调用了 `IFn` 接口的 `applyTo` 方法,详见:[clojure.lang.AFn](https://github.com/clojure/clojure/blob/b4ea0f824b2eea039dfc06b796ed601e35cbeab6/src/jvm/clojure/lang/AFn.java#L143-L425)、[clojure.lang.Compiler](https://github.com/clojure/clojure/blob/b4ea0f824b2eea039dfc06b796ed601e35cbeab6/src/jvm/clojure/lang/Compiler.java#L4215-L4231) 和 [clojure.core/apply](https://github.com/clojure/clojure/blob/b4ea0f824b2eea039dfc06b796ed601e35cbeab6/src/clj/clojure/core.clj#L662-L675)。Clojure 虽然是 Java 写的,但代码很易读。
+[^2]: 之后会讲到,列表实际上是用 cons 对构造的。