【发布时间】:2010-09-19 17:29:21
【问题描述】:
许多宏示例似乎是关于隐藏 lambda,例如在 CL 中打开文件。我正在寻找宏的一些更奇特的用途,特别是在 PLT 方案中。我想了解一下何时考虑使用宏还是使用函数。
【问题讨论】:
-
PLT 方案现在称为 Racket。见racket-lang.org
许多宏示例似乎是关于隐藏 lambda,例如在 CL 中打开文件。我正在寻找宏的一些更奇特的用途,特别是在 PLT 方案中。我想了解一下何时考虑使用宏还是使用函数。
【问题讨论】:
需要宏来实现新的控制结构和新的绑定结构。
因此在http://planet.plt-scheme.org 寻找这些类型的结构。 在 PLaneT,您可以同时浏览文档和代码。
新控制结构示例:
http://planet.plt-scheme.org/package-source/soegaard/control.plt/2/0/planet-docs/manual/index.html
要查找新绑定形式的示例,请查找以“with-”开头的宏。 PLaneT 的 math.plt 中也有一个有用的示例。
; Within a (with-modulus n form1 ...) the return values of
; the arithmetival operations +, -, * and ^ are automatically
; reduced modulo n. Furthermore (mod x)=(modulo x n) and
; (inv x)=(inverse x n).
; Example: (with-modulus 3 (^ 2 4)) ==> 1
(define-syntax (with-modulus stx)
(syntax-case stx ()
[(with-modulus e form ...)
(with-syntax ([+ (datum->syntax-object (syntax with-modulus) '+)]
[- (datum->syntax-object (syntax with-modulus) '-)]
[* (datum->syntax-object (syntax with-modulus) '*)]
[^ (datum->syntax-object (syntax with-modulus) '^)]
[mod (datum->syntax-object (syntax with-modulus) 'mod)]
[inv (datum->syntax-object (syntax with-modulus) 'inv)])
(syntax (let* ([n e]
[mod (lambda (x) (modulo x n))]
[inv (lambda (x) (inverse x n))]
[+ (compose mod +)]
[- (compose mod -)]
[* (compose mod *)]
[square (lambda (x) (* x x))]
[^ (rec ^ (lambda (a b)
(cond
[(= b 0) 1]
[(even? b) (square (^ a (/ b 2)))]
[else (* a (^ a (sub1 b)))])))])
form ...)))]))
【讨论】:
我只使用 Scheme 宏 (define-syntax) 来处理诸如更好的 lambda 语法之类的小事:
(define-syntax [: x]
(syntax-case x ()
([src-: e es ...]
(syntax-case (datum->syntax-object #'src-: '_) ()
(_ #'(lambda (_) (e es ...)))))))
让你写作
[: / _ 2] ; <-- much better than (lambda (x) (/ x 2))
Dan Friedman 使用宏实现了令人费解的 OO:http://www.cs.indiana.edu/~dfried/ooo.pdf
但老实说,我定义的所有 有用 宏都是从 Paul Graham's On Lisp 窃取的,并且通常使用 defmacro(PLT 方案中的 define-macro)更容易编写。例如,aif 与 define-syntax 相当难看。
(define-syntax (aif x)
(syntax-case x ()
[(src-aif test then else)
(syntax-case (datum->syntax-object (syntax src-aif) '_) ()
[_ (syntax (let ([_ test]) (if (and _ (not (null? _))) then else)))])]))
define-syntax 很奇怪,因为它只适用于非常简单的宏,您很高兴无法捕获变量;以及非常复杂的宏 DSL,您很高兴无法轻松捕获变量。在第一种情况下,您想编写代码而不考虑它,而在第二种情况下,您已经对 DSL 进行了足够的考虑,因此您愿意用不是 Scheme 的 syntax-rules/syntax-case 语言编写其中的一部分为了避免神秘的错误。
但我在Scheme 中没有太多使用宏。 Idiomatic Scheme 非常实用,以至于很多时候您只想编写一个函数式程序,然后隐藏一些 lambda。我上了函数式训练,现在相信如果你有一种惰性语言或 lambda 的良好语法,即使这样也没有必要,所以宏在纯函数式风格中并不是那么有用。
所以我推荐Practical Common Lisp 和On Lisp。如果您想使用 PLT Scheme,我认为他们的大多数 defmacro 宏都可以与 define-macro 一起使用。或者只使用 Common Lisp。
【讨论】:
aif 定义起来会更干净(而且卫生!):`(define-syntax-parameter it #f) (define-syntax aif (syntax-parser [ (_ test then else) #'(let ([result test]) (syntax-parameterize ([it (make-rename-transformer result)]) (if result then else)))]))
我将开始回答最后一个问题。何时使用宏而不是函数。宏做函数不能做的事情,函数做宏不能做的事情,所以很难混合它们,但让我们更深入。
当你想要评估参数时使用函数,当你想要不评估参数时使用宏。这不是很有用,是吗?当你想以不同的方式写东西,当你看到一个模式并且你想抽象时,你会使用宏。例如:我为 foo 的不同值和类似的主体定义了三个函数,称为 foo-create、foo-process 和 foo-destroy,其中唯一的变化是 foo。有一个模式,但函数级别太高,所以你创建一个宏。
根据我的拙见,Scheme 中的宏与其他 Lisp 中的使用一样多,例如 Common Lisp 或 Clojure。我想这证明了卫生宏可能不是一个好主意,在这里我不同意 Paul Graham 的原因。这不是因为有时您想弄脏(不卫生),而是因为卫生宏最终变得复杂或令人费解。
【讨论】:
Practical Common Lisp,作者 Peter Seibel,对宏有很好的介绍。在 Lisp 上,由 Paul Graham 撰写,可能是更复杂示例的良好来源。另外,看看 Common Lisp 中的内置宏。
【讨论】:
Automata via Macros 论文介绍了通过 Scheme 中的宏实现有限状态机的函数式编程珍珠。
本书The Reasoned Schemer 以完整的基于宏的miniKanren 实现结束,miniKanren 是书中使用的逻辑编程语言。 This paper 比书中更正式、更简洁地介绍了 miniKanren 及其实现。
【讨论】:
Common Lisp 的宏with-slots 是一个不是变相的 lambda 形式的更高级宏的示例,它使对象插槽访问看起来像普通的变量访问:
(with-slots (state door) car
(when (eq state :stopped)
(setq state :driving-around)
(setq door :closed)))
请注意,这与将插槽值绑定到局部变量并访问它们不同,因为 with-slots 允许您通过 SETQ 更改插槽并立即查看外部更改。 p>
【讨论】:
当我以前在我的手掌上做很多计划时,我有一个 curry 宏。这很方便。
【讨论】:
方案宏可让您添加原始语言作者未包含的功能;这就是宏背后的全部理念。
这是一个小例子:PLT Scheme 提供了一种用于编写演示文稿的语言,称为幻灯片。我使用宏将幻灯片编号与幻灯片关联起来,以便更轻松地管理它们。
【讨论】:
我编写了一个提供中缀语法的宏。没有什么太花哨的;没有优先权。虽然我通常对前缀语法没问题,但我更喜欢 的中缀。
【讨论】:
当程序不够用时,我会使用它们。
【讨论】: