代码之家  ›  专栏  ›  技术社区  ›  Coderino Javarino

是否有可能/使用卫生宏进行编译时计算优化的示例有哪些?

  •  1
  • Coderino Javarino  · 技术社区  · 6 年前

    我一直在通读 https://lispcast.com/when-to-use-a-macro ,并声明(关于clojure的宏)

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

    我抬头一看,克洛朱尔似乎有不卫生的宏。这也适用于卫生型的吗?尤其是谈论阴谋。就我所理解的卫生宏而言,它们只会转换语法,但代码的实际执行会推迟到运行时,不管发生什么。

    2 回复  |  直到 6 年前
        1
  •  1
  •   blihp    6 年前

    对。 Macro hygiene 只是指宏扩展是否可以意外捕获标识符。无论宏是否健康,编译时都会发生常规宏扩展(与读取器宏扩展相反)。宏扩展用宏执行的结果替换宏的代码。它们的两个主要用例是转换语法(即dsl),通过消除运行时的计算或同时消除这两者来提高性能。

    想到几个例子:

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

    您还可以结合这些示例并使用一个可记忆的sql解析器。几乎任何在编译时拥有所有必要输入并因此能够计算结果的场景都是候选的。

        2
  •  0
  •   tfb    6 年前

    是的,卫生宏可以做这种事情。例如,这里有一个宏 plus 在球拍中 + 除此之外,在宏扩展时,它对相邻的文字数字序列求和。所以它完成了一些您可能期望在运行时宏扩展时完成的工作(所以,实际上,是在编译时)。比如说

    (plus a b 1 2 3 c 4 5)
    

    扩展到

    (+ a b 6 c 9)
    

    关于这个宏的一些注释。

    • 这可能不是很地道的球拍,因为我是一个基本上未经改造的cl黑客,这意味着我住在一个洞穴里,穿着兽皮,经常说“ug”。尤其是我确信我应该使用 syntax-parse 但我不明白。
    • 甚至可能都不对。
    • 算术有一些微妙之处,这意味着这个宏可以返回不同于 + 。特别地 + 定义为从左到右添加成对,而 通常不会:所有的文字都是首先添加的(假设您已经添加了(需要racket/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 ,因为这两个字面值先被添加,然后溢出。
    • 总的来说,这是一件无用的事情:我认为,可以肯定的是,任何一个合理的编译器都会为您进行这种优化。它的唯一目的是显示可以检测并编译掉编译时常量。
    • 值得注意的是,至少从我这样的穴居人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]))