v-r*_*rob 2 lisp macros scheme
在过去的几天里,我一直在学习一些 Lisp(主要是Scheme),被这些语言所提供的强大力量所吸引。当然,宏应该是这种力量所基于的最强大的功能。据我所知,学习使用宏很像首先学习如何使用一流的函数。当您学习如何使用一流函数时,它们开始适用于任何地方,以至于很难回到没有这样概念的命令式语言。例如,编写 pre-lambda Java 代码现在对我来说非常痛苦。
考虑到这种一流函数的类比,我非常震惊地发现 Lisp 宏并不是一流的。
例如,我可以通过以下方式轻松地在Scheme中使用一等函数:
(map (lambda (x) (+ x 1)) '(1 2 3))
Run Code Online (Sandbox Code Playgroud)
现在用某个宏的名称替换该 lambda,Scheme 会抱怨您正在使用宏作为变量名。鉴于我无法像数据一样传递宏,它们绝对是二等的。当然,我什至不确定随机宏在这种情况下应该如何工作,但这不是重点。
我总是对仅编译时的构造保持警惕,因为这通常表明语言存在一些限制。因此,我自然想知道宏如何成为一流的运行时对象。我们已经可以用 lambda 来做到这一点了。例如,在此方案代码中, 和 都or将eval-or打印1并返回#t,但eval-or需要在调用站点上使用撇号:
(or
(begin (display "1") (= 5 5))
(begin (display "2") (= 5 6)))
(define eval-or
(lambda (a b)
(if (eval a) #t (eval b))))
(eval-or
'(begin (display "1") (= 5 5))
'(begin (display "2") (= 5 6)))
Run Code Online (Sandbox Code Playgroud)
我不熟悉 的细节eval,但我确实知道它不保留词法范围。但也许如果我们向Scheme 添加某种特殊的构造,例如lazy-lambda,允许您选择何时评估参数,同时仍然保留普通 lambda 的所有作用域属性?然后你可以做类似的事情:
(define lazy-or
(lazy-lambda (a b)
(if (value a) #t (value b))))
(lazy-or
(begin (display "1") (= 5 5))
(begin (display "2") (= 5 6)))
Run Code Online (Sandbox Code Playgroud)
在我看来,这应该能够执行卫生宏可以执行的所有相同操作。例如,我可以创建 DSL 或全新形式的控制流,就像宏一样。它不使用eval,因此我可以引用调用者的词法范围内的内容。但此外,它也是完全一流的——我可以传递lazy-or给函数,就像使用+. 它看起来像 Lisp 宏的强大功能,但完全是一流的和运行时的。
当然,如果这么简单的话,早就有人想到了不是吗?我完全预计这个想法可能存在一些根本性的缺陷(考虑到我仅有的 Lisp 经验,更是如此);如果是这样,如果有人能指出这一点,我将不胜感激。
否则,谁能给我一个具体的例子,说明宏可以做一些非常重要的事情,而这个假设却lazy-lambda不能?
(注:不是Lisp 宏只是语法糖吗?或Lisp 宏为何如此特别?的重复。我问的是 Lisp 中未找到的假设语言功能,该功能可能比宏更好,但不会损失重要的可用性,而不是问关于宏和函数之间的区别)
否则,谁能给我一个具体的例子,其中宏可以做一些非常重要的事情,而这个假设的惰性 lambda 不能做?
宏可以做的是重塑语言方式,而不仅仅是评估控制。
关于宏参数的重要之处不是它们没有被求值,而是它们是未分配含义的源代码(因此它们没有被求值)。宏可以分析参数并产生不同于“这个被评估,而那个不被评估”的含义。
不过,我们必须退后一步:“宏观论证”是一种幻觉。经典的非卫生宏观系统模拟参数。在幕后,宏名称与扩展器函数相关联。该函数将整个宏调用形式作为一个参数。defmacro运算符本身就是一个宏,它编写执行解构的代码,这使得宏看起来像具有带参数的参数。
宏已用于实现:
面向对象的编程系统
用于循环和迭代的领域特定语言
模式匹配符号
可分配的位置
Lisp 方言中基于宏的模式匹配示例:
5> (if-match (likes @x @x) '(1 2 2) x)
nil
6> (if-match (likes @x @x) '(1 2 3) x)
nil
7> (if-match (likes @x @x) '(1 2 2) x)
nil
8> (if-match (likes @x @x) '(likes 2 2) x)
2
Run Code Online (Sandbox Code Playgroud)
在引擎盖下:
9> (expand '(if-match (likes @x @x) '(likes 2 2) x))
(let* (#:result-0112
x)
(if (if (consp '(likes 2 2))
(let ((#:g0106 (car '(likes 2 2)))
(#:g0107 (cdr '(likes 2 2))))
(if (equal #:g0106 'likes)
(if (consp #:g0107)
(let ((#:g0108 (car #:g0107))
(#:g0109 (cdr #:g0107)))
(sys:setq x #:g0108)
(if (consp #:g0109)
(let ((#:g0110 (car #:g0109))
(#:g0111 (cdr #:g0109)))
(if (equal #:g0110 x)
(if (equal #:g0111 '())
(progn (sys:setq #:result-0112
x)
t))))))))))
#:result-0112
()))
Run Code Online (Sandbox Code Playgroud)
我们可以看到,虽然宏的'(likes 2 2)和x参数if-match在扩展中直接出现(它们被求值),但(likes @x @x)模式表达式并不是简单地、延迟地或以其他方式求值。它变成了代码:遍历输入对象并执行测试以确认该对象是否与模式匹配的代码,并准备x该事件中的值。
附带说明一下,您可能认为代码看起来很糟糕,但由于是(likes 2 2)文字常量,编译器将其简化为一条指令:
10> (flow '(if-match (likes @x @x) '(likes 2 2) x)
compile-toplevel
disassemble)
data:
0: 2
syms:
code:
0: 10000400 end d0
instruction count:
1
#<sys:vm-desc: 90eff30>
Run Code Online (Sandbox Code Playgroud)