是的,卫生宏可以做这种事情。例如,这里有一个名为 plus 在 Racket 中的宏,它类似于 +,只是在宏扩展时,它对相邻文字数字的序列求和。所以它做了一些你可能期望在运行时在宏扩展时完成的工作(所以,实际上,在编译时)。所以,例如
(plus a b 1 2 3 c 4 5)
扩展到
(+ a b 6 c 9)
关于这个宏的一些注释。
- 这可能不是很惯用的 Racket,因为我基本上是一个未改造的 CL 黑客,这意味着我住在山洞里,穿着兽皮,经常说“ug”。特别是我确定我应该使用
syntax-parse,但我无法理解。
- 甚至可能都不对。
- 算术上有一些微妙之处,这意味着该宏可以返回与
+ 不同的结果。特别是 + 被定义为从左到右成对添加,而 plus 通常不会:特别是首先添加所有文字(假设您已经完成(需要球拍/flonum,并且+max.0 &c 有与我的机器上的值相同),然后(+ -max.0 1.7976931348623157e+308 1.7976931348623157e+308) 的值为1.7976931348623157e+308,而(plus -max.0 1.7976931348623157e+308 1.7976931348623157e+308) 的值为+inf.0,因为首先添加了两个文字,这会溢出。
- 一般来说,这是一件无用的事情:我认为,可以安全地假设任何合理的编译器都会为您进行此类优化。它的唯一目的是表明可以检测并编译掉编译时常量。
- 值得注意的是,至少从像我这样的caveman-lisp用户的角度来看,你可以像
+一样对待这个,因为syntax-case中的最后一个:例如(apply plus ...)(虽然当然,在这种情况下不会发生任何巧妙的优化)。
这里是:
(require (for-syntax racket/list))
(define-syntax (plus stx)
(define +/stx (datum->syntax stx +))
(syntax-case stx ()
[(_)
;; return additive identity
#'0]
[(_ a)
;; identity with one argument
#'a]
[(_ a ...)
;; the interesting case: there's more than one argument, so walk over them
;; looking for literal numbers. This is probably overcomplicated and
;; unidiomatic
(let* ([syntaxes (syntax->list #'(a ...))]
[reduced (let rloop ([current (first syntaxes)]
[tail (rest syntaxes)]
[accum '()])
(cond
[(null? tail)
(reverse (cons current accum))]
[(and (number? (syntax-e current))
(number? (syntax-e (first tail))))
(rloop (datum->syntax stx
(+ (syntax-e current)
(syntax-e (first tail))))
(rest tail)
accum)]
[else
(rloop (first tail)
(rest tail)
(cons current accum))]))])
(if (= (length reduced) 1)
(first reduced)
;; make sure the operation is our +
#`(#,+/stx #,@reduced)))]
[_
;; plus on its own is +, but we want our one. I am not sure this is right
+/stx]))
实际上可以更积极地执行此操作,以便将(plus a b 1 2 c 3) 变成(+ a b c 6)。这可能具有更令人兴奋的可能得到不同答案的含义。值得注意的是 CL 规范 says about this:
对于数学上关联(并且可能是可交换)的函数,符合要求的实现可以以任何与关联(并且可能是可交换)重排一致的方式处理参数。这不会影响评估参数形式的顺序[...]。未指定的只是处理参数值的顺序。这意味着应用自动强制转换的实现可能会有所不同[...]。
所以这样的优化在 CL 中显然是合法的:我不清楚它在 Racket 中是否合法(尽管我认为应该是)。
(require (for-syntax racket/list))
(define-for-syntax (split-literals syntaxes)
;; split a list into literal numbers and the rest
(let sloop ([tail syntaxes]
[accum/lit '()]
[accum/nonlit '()])
(if (null? tail)
(values (reverse accum/lit) (reverse accum/nonlit))
(let ([current (first tail)])
(if (number? (syntax-e current))
(sloop (rest tail)
(cons (syntax-e current) accum/lit)
accum/nonlit)
(sloop (rest tail)
accum/lit
(cons current accum/nonlit)))))))
(define-syntax (plus stx)
(define +/stx (datum->syntax stx +))
(syntax-case stx ()
[(_)
;; return additive identity
#'0]
[(_ a)
;; identity with one argument
#'a]
[(_ a ...)
;; the interesting case: there's more than one argument: split the
;; arguments into literals and nonliterals and handle approprately
(let-values ([(literals nonliterals)
(split-literals (syntax->list #'(a ...)))])
(if (null? literals)
(if (null? nonliterals)
#'0
#`(#,+/stx #,@nonliterals))
(let ([sum/stx (datum->syntax stx (apply + literals))])
(if (null? nonliterals)
sum/stx
#`(#,+/stx #,@nonliterals #,sum/stx)))))]
[_
;; plus on its own is +, but we want our one. I am not sure this is right
+/stx]))