summaryrefslogtreecommitdiff
path: root/content
diff options
context:
space:
mode:
Diffstat (limited to 'content')
-rw-r--r--content/article/tutorial-clj-2.md.md149
1 files changed, 149 insertions, 0 deletions
diff --git a/content/article/tutorial-clj-2.md.md b/content/article/tutorial-clj-2.md.md
new file mode 100644
index 00000000..57d67931
--- /dev/null
+++ b/content/article/tutorial-clj-2.md.md
@@ -0,0 +1,149 @@
++++
+title = 'Coffee Break Clojure, Vol.2'
+subtitle = "Forms O' Plenty"
+slug = 'tutorial-clj-2'
+tags = ["Lisp","编程"]
+categories = '茶歇蔻芥'
+date = 2026-09-23T23:15:12+08:00
+draft = true
+toc = true
++++
+
+[上一篇](http://localhost:1313/article/tutorial-clj-1/)我们配置好了 REPL,可以随时开始编写 Clojure 代码并执行。[第零篇](/article/tutorial-clj-0/)提到,被求值(Eval)的列表叫作形式(form),形式的第一个元素是操作符,剩余的是参数。根据操作符的不同,形式被分为函数形式、Lambda 形式、宏形式和特殊形式。今天的文章就来介绍这几种形式。
+
+## 函数形式
+
+不难理解,函数形式就是操作符为函数的形式,比如这个 `println` 形式:
+
+```clojure
+(println "Hello World")
+```
+
+我们知道 `"Hello World"` 是字符串,那 `println` 是什么类型?在其他语言里我们可能很少考虑函数名本身的类型,但别忘了 Lisp 的「代码即数据」本质。要把待求值的形式当作数据而非形式处理,只需要把它 `quote` 起来。
+
+这里要引入 Clojure 的 `type` 函数,它返回输入值的类型。我们不妨把刚才的形式 `quote` 起来,然后获取他的第一个元素 `println`,看看它是什么类型。
+
+```clojure
+(type (first '(println "Hello World")))
+;; => clojure.lang.Symbol
+```
+
+类型是 `Symbol`(符号)!这是 Lisp 中的一种特殊数据类型,和其他的原子不同,符号不是自求值(self-evaluating)的。意思是说,对 `"string"` 求值得到的是 `"string"` 本身,但对符号求值得到的是符号指向的其他值。如果要得到符号本身,就要用 `quote`。
+
+符号指向的值可以是某个自求值对象,也可以是列表;这个列表可以是数据,也可以是个函数,还可以是宏。要定义一个符号也很简单,只需要用到 `def`。
+
+```clojure
+(def my-symbol 123)
+;; => #'user/my-symbol
+
+123
+;; => 123
+
+my-symbol
+;; => 123
+
+(eval 'my-symbol)
+;; => 123
+```
+
+可见符号和它指向的数据本身是等价的,对 `123` 求值和对 `my-symbol` 求值得到的结果都是 `123`。
+
+接下来试试让符号指向函数:
+
+```clojure
+(def my-fn
+ (fn [a b]
+ (+ a b)))
+;; => #'user/my-fn
+
+(fn [a b] (+ a b))
+;; => #object[user$eval170$fn__171 0x6f8e0cee "user$eval170$fn__171@6f8e0cee"]
+
+my-fn
+;; => #object[user$my_fn 0x3df978b9 "user$my_fn@3df978b9"]
+
+(my-fn 1 2)
+;; => 3
+```
+
+`fn` 是个宏(macro),稍后我会讲到它为什么必须是个宏而不能是函数。你可以把它理解为其他语言里的匿名函数,或者 Lambda 表达式。实际上 `fn` 就是 Common Lisp 和 Scheme 里的 `lambda`,不过换了个名字。
+
+直接对 `fn` 形式求值得到的是形如 `#object[...]` 的字符串,这个字符串是 JVM 上某个数据的文字表示。像函数这种数据没办法像字符串和数字那样直接表示出来,于是就用这种标记表示。
+
+输出这种标记对程序而言没有意义,使用函数的正确方式当然是调用它。上述例子中的 `(my-fn 1 2)` 就是个函数形式,第一个元素是指向函数的符号,剩下的数据作为参数传递给符号指向的函数。
+
+既然符号本身也是数据,那么如果形式的第一个元素不是符号,而是返回符号的函数调用,能否正常调用函数呢?还记得 `eval` 本身也是个函数吗?
+
+```clojure
+((eval 'my-fn) 1 2)
+;; => 3
+```
+
+之前我们提到对 `(eval (quote x))` 求值和对 `x` 本身求值是等价的,这里也成立。对 `my-fn` 求值返回的是个符号,对 `(eval 'my-fn)` 求值得到的也是个符号。只要形式的第一个元素是指向函数的符号,就能完成函数调用。
+
+Clojure 有个 `symbol` 函数,输入字符串,它会返回特定名称的符号。
+
+```clojure
+((eval (symbol "my-fn")) 1 2)
+;; => 1 2
+```
+
+根据条件返回不同类型的符号也是可以的:
+
+```clojure
+(def a 1)
+;; => #'user/a
+((eval (if (> a 1) '+ '-)) 2 1)
+;; => 1
+```
+
+好了好了,没人会这么写程序的。你现在知道,函数形式就是第一个元素为指向函数的符号的形式,对函数形式求值就是调用函数。还记得刚才让一个符号指向一个值的 `def` 吗?如果要用来声明函数,还有更简便的写法:
+
+```clojure
+(def my-fn
+ (fn [a b]
+ (+ a b)))
+
+;; 等价于
+(defn my-fn [a b]
+ (+ a b))
+```
+
+写 Clojure 函数的时候你多半只会用到 `defn`,但不要忘了函数名和变量名一样,都只是符号而已。
+
+记住这个原理你能做到很多有趣的事情。比方说,在其他语言里我们经常对值做一些操作,然后赋值给变量,就像这样:
+
+```clojure
+(def a (reduce + '(1 2 3)))
+;; => #'user/a
+
+a
+;; => 6
+```
+
+我们对列表 `(1 2 3)` 做了累加操作,变成了 `6`,然后赋值给 `a`。函数也可以这么干,比如 `partial` 函数可以让我们提前填充函数的某些参数,然后返回新的函数:
+
+```clojure
+(def plus-one (partial + 1))
+;; => #'user/plus-one
+
+(plus-one 3)
+;; => 4
+
+((partial + 1) 3)
+;; => 4
+```
+
+我们对函数 `+` 做了 `partial` 操作,给它提前填上了一个参数 `1`。调用得到的新函数就相当于调用 `(+ 1 ...)`。在其他语言里,要做到类似的事情,你可能要这么写:
+
+```clojure
+(defn plus-one [num]
+ (+ 1 num))
+```
+
+## Lambda 形式
+
+## 宏形式
+
+## 特殊形式
+