summaryrefslogtreecommitdiff
path: root/content/notes
diff options
context:
space:
mode:
authoreltrac <hi@eltr.ac>2026-03-06 13:32:45 +0800
committereltrac <hi@eltr.ac>2026-03-06 13:32:45 +0800
commit9facfd64edada48e31163718040532a9e1b7dd69 (patch)
tree668caa56407cb91afa198933f6c3e3b80ebe95b0 /content/notes
parent064f40c2acc88844358fc2b54fa21d2f64c74139 (diff)
docs: new note
Diffstat (limited to 'content/notes')
-rw-r--r--content/notes/defn-and-defmacro-in-clojure.md72
1 files changed, 72 insertions, 0 deletions
diff --git a/content/notes/defn-and-defmacro-in-clojure.md b/content/notes/defn-and-defmacro-in-clojure.md
new file mode 100644
index 0000000..30e2da4
--- /dev/null
+++ b/content/notes/defn-and-defmacro-in-clojure.md
@@ -0,0 +1,72 @@
++++
+date = '2026-03-06T13:06:53+08:00'
+draft = false
+title = 'Clojure 中的函数和宏'
+tags = ['Clojure']
++++
+
+由于在其他编程语言里很少接触宏(macro),直接触过 C 语言里的宏,即 `#define`。C 语言里的宏在编译器预处理时被处理,由于我没有 C 的开发经验,所以最常用宏的场景仅仅是用 `#define` 来定义常量。因此,在学习 Clojure 时,我对函数和 Lisp 语言里常见的宏产生了些许疑问。
+
+函数和宏到底有什么区别?在 Clojure 里,它们都以相似的语法被定义。
+
+```clojure
+;; 这是一个函数
+(defn do-something [arg] ...)
+
+;; 这是一个宏
+(defmacro my-macro [arg] ...)
+```
+
+调用函数和使用宏的方式也一致。
+
+```clojure
+(do-something arg)
+(my-macro arg)
+```
+
+而且它们也都支持用 `&` 来标记可变数量的参数。
+
+## 区别
+
+最终在 [StackOverflow](https://stackoverflow.com/questions/3667403/what-is-the-difference-between-defn-and-defmacro)[^1] 上找到了解答。
+
+简单来说,函数和宏被调用时:
+
+- **函数**的参数会先被计算(evaluate),然后再用计算得到的值执行函数体。
+- **宏**会先进行宏展开,将当前代码变成宏定义的形式,再计算整段代码。
+
+以下是几个说明这种区别的例子。
+
+## 一
+
+用于逻辑运算的 `or` 是宏,并且**不能**是函数,因为 `(or a b)` 在计算时,如果 `a` 的结果已经为 `true`,就不会继续计算 `b`。如果 `or` 是函数,那么就要把 `a` 和 `b` 都计算之后才会计算 `or` 函数本身,这对逻辑运算来说是低效且不合理的。
+
+## 二
+
+由于函数的参数会先被计算,所以参数不允许出现无效的表达式,而宏则是允许的。
+
+设有一个宏 `(mymacro (1 2 +))` 会被展开为 `(+ 1 2)`,而 `(1 2 +)` 本身是无效的,因为没有 `1` 这个函数或者宏。假如 `mymacro` 是函数而不是宏,就会报错,因为无法计算出参数 `(1 2 +)` 的值;而宏进行展开得到的 `(+ 1 2)` 就能够被计算。
+
+## 三
+
+假设有这样一个宏:
+
+```clojure
+(defmacro twice [e] `(do ~e ~e))
+```
+
+这个宏把 `(twice (println "foo"))` 展开为 `(do (println "foo") (println "foo"))`,也就是打印了两次 `foo`。
+
+如果是函数的话……
+
+```clojure
+(defn twice [e] `(do ~e ~e))
+```
+
+调用函数 `(twice (println "foo"))` 的话,会先计算 `(println "foo")` 的值,由于 `println` 没有返回值,所以只会得到 `nil`。函数体最终执行的代码是 `(do nil nil)`。
+
+---
+
+总而言之,**宏**是用来转换代码,或者说给代码变形的,也就是 Transform;而**函数**是用来接收参数并执行代码的。
+
+[^1]: [Internet Archive 链接](https://web.archive.org/web/20241011075734/https://stackoverflow.com/questions/3667403/what-is-the-difference-between-defn-and-defmacro)