diff options
| author | eltrac <hi@eltr.ac> | 2026-09-16 23:45:54 +0800 |
|---|---|---|
| committer | eltrac <hi@eltr.ac> | 2026-09-16 23:45:54 +0800 |
| commit | a6aacbf06d4756e965994fc38505bbf49e6ea97e (patch) | |
| tree | 095ad8580d25519646d516c6a57918d93735b9b7 /content | |
| parent | fafc1f91d8281185a810e225d9158552e78608da (diff) | |
[edit] fix terminology
Diffstat (limited to 'content')
| -rw-r--r-- | content/article/tutorial-clj-0.md | 18 |
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 对构造的。 |
