From 395a88d3a5bbd48f5264c94a353f2fccbf07b827 Mon Sep 17 00:00:00 2001 From: eltrac Date: Thu, 24 Sep 2026 00:38:55 +0800 Subject: [draft] new --- content/article/tutorial-clj-2.md.md | 149 +++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 content/article/tutorial-clj-2.md.md (limited to 'content/article') 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 形式 + +## 宏形式 + +## 特殊形式 + -- cgit v1.2.3