使用卫生宏进行编译时计算优化的例子有可能吗?

时间:2019-02-06 20:42:42

标签: optimization macros scheme lisp

我一直在阅读https://lispcast.com/when-to-use-a-macro,它指出(关于clojure的宏)

  

另一个示例是在编译时作为优化来执行昂贵的计算

我抬起头,似乎clojure的宏不卫生。这也可以适用于卫生的吗?特别是谈论Scheme。据我了解,卫生宏只能转换语法,但是无论如何,代码的实际执行都将推迟到运行时。

2 个答案:

答案 0 :(得分:1)

是的。 Macro hygiene仅指宏扩展是否可以意外捕获标识符。不管宏是否卫生,在编译时都会进行常规的宏扩展(与读取器宏扩展相对)。宏扩展将宏的代码替换为正在执行的结果。它们的两个主要用例是转换语法(即DSL),通过消除运行时或同时消除两者的计算来提高性能。

想到一些例子:

  1. 您倾向于使用角度(以度为单位)编写代码,但是所有计算实际上都是以弧度为单位。您可以让宏在编译时消除这些琐碎但不必要的(在运行时)转换。
  2. Memoization是宏可以用于计算优化的一个广泛示例。
  3. 您有一个字符串,表示要解析的SQL语句或复杂的文本数学表达式,甚至可能在编译时执行。

您还可以结合示例并使用一个备忘的SQL解析器。几乎所有在编译时都具有所有必要输入并因此可以计算结果的方案就是一个候选方案。

答案 1 :(得分:0)

是的,卫生宏可以执行此类操作。作为示例,这里是一个在Racket中称为plus的宏,它类似于+,不同之处在于,在宏扩展时,它会将相邻文字数字的序列求和。因此,它完成了您可能希望在运行时在宏扩展时(因此有效地在编译时)完成的一些工作。例如,

(plus a b 1 2 3 c 4 5)

扩展到

(+ a b 6 c 9)

有关此宏的一些说明。

  • 这可能不是很惯用的球拍,因为我是一个未改组的CL黑客,这意味着我住在山洞里,穿动物皮,经常说“ ug”。我特别确定我应该使用syntax-parse,但我听不懂。
  • 可能甚至不正确。
  • 算术上有一些微妙之处,这意味着该宏可以返回与+不同的结果。特别地,+被定义为从左到右成对添加,而plus则通常不是这样:特别是所有文字都首先添加了(假设您已经完成(需要球拍/发球,而{{ 1}}&c具有与在我的计算机上相同的值),则+max.0的值为(+ -max.0 1.7976931348623157e+308 1.7976931348623157e+308),而1.7976931348623157e+308的值为(plus -max.0 1.7976931348623157e+308 1.7976931348623157e+308),因为首先添加两个文字,这会溢出。
  • 通常这是没有用的事情:我认为可以肯定地认为,任何合理的编译器都将为您做这些优化。这样做的唯一目的是证明可以进行检测并编译掉编译时常量。
  • 值得注意的是,至少从像我这样的穴居人用户的角度来看,由于+inf.0中的最后一个,您可以像+那样对待它:它可以说{{1 }}(例如,尽管在这种情况下不会进行明智的优化)。

这里是:

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])) 。这可能具有更令人兴奋的可能得到不同答案的含义。值得注意的是CL规范says about this

  

对于在数学上是关联的(并且可能是可交换的)函数,一致的实现可以以与关联的(并且可能是可交换的)重排一致的任何方式来处理自变量。这不会影响参数形式的计算顺序。未指定的只是参数值的处理顺序。这意味着应用自动强制的实现方式可能会有所不同。

因此,这样的优化在CL中显然是合法的:我不确定在Racket中是否合法(尽管我认为应该如此)。

(plus a b 1 2 c 3)