宏扩展的执行逻辑初探(一)
宏是怎么扩展的呢?
比较流行的编程语言中,Java
,Python
都没有定义宏的功能。c
语言中的宏限制比较大,
没有Lisp
中的宏强大。以下提到的宏,指的是Lisp方言中的宏。
宏的求值可以分为两个步骤
- 扩展(expand)–编译期
- 求值(eval) –运行期
这看起来很简单,但是实际写macro的时候,就会有很多的困惑。这里的主要矛盾就是理解 以下两个过程,
- 在一个宏的内部调用另一个函数
- 在一个宏的内部调用另一个宏
不同的lisp方言有不同的实现。以下用Clojure来研究宏的用法。
宏内部调用函数(call function inside macro)
宏扩展阶段,就会调用函数进行求值。
1 |
|
运行结果如下,可以看到宏扩展阶段已经调用+
函数进行了求值。
1 |
|
这里的+
是一个函数。
宏内部调用宏(call macro inside macro)
继续上面的例子,我们定义一个加法宏,然后在fool的内部调用该宏。
1 |
|
运行这段代码会得到以下的错误信息。重点就是理解这里为什么会报错。
1 |
|
这里报错说是Symbol无法转换为数字。可是我们明明输入的是2。
事实的过程如下,
- 扩展 (fool 2)
- 进入fool的body
- 扩展 (plus x 1) — 这里x是一个Symbol,而不是x-value
- 进入宏plus的body
- 对(+ x 1)求值 —到这里报错了,因为x是一个Symbol。
所以,如果直接在一个宏的内部调用另一个宏,很可能编译都无法通过。因为编译期会对宏进行扩展, 但不会绑定实参。
那么怎么正确的使用呢,这里引入了一个概念叫emit a macro call inside a macro
。
1 |
|
结果如下,只扩展一次,会得到(plus 2 1)
, 而完全扩展会得到3
。
1 |
|
具体过程如下,
- 扩展(fool 2)
- 进入fool的body
- 求值(list ‘plus x-value 1) – 这里x-value是2
- 得到(plus x-value 1) – 这里x-value是2
- 发现仍然是一个宏,继续扩展
- 进入plus的body
- 求值(+ x-value 1)为3 – 这里x-value是2
语法引用(Syntax Quote)
正是因为需要频繁的使用list来进行宏调用,因此发明了这样的语法糖。
1 |
|
宏扩展的执行逻辑初探(一)
https://threelambda.com/2018/11/09/minilisp/