【问题标题】:Is it possible / what are examples of using hygienic macros for the compile time computational optimization?是否有可能/有哪些使用卫生宏进行编译时计算优化的例子?
【发布时间】:2019-02-06 20:42:42
【问题描述】:

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

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

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

【问题讨论】:

  • 如果宏不执行,它们如何转换语法?简单的 Racket 示例:(define-syntax compile-time-compute (λ (stx) (with-syntax (((_ exp) stx)) #`(quote #,(eval-syntax #'exp))))),那么当您编写 (compile-time-compute (+ 1 2)) 时,它会将语法转换为 (quote 3)
  • @PetSerAl 不知道with-syntax;看着它,它在 r6rs 中受支持,但在 r7rs 中不支持。但我认为编译时计算需要它(或在特定实现中等效),对吧?我没有对syntax-rules做类似的事情
  • with-syntax 只需要解压语法对象 (stx) 并从中提取第二个列表元素 (exp)。如果您能够将任意λ 表单传递给define-syntax,那么该表单可以表示任意复杂计算,这将在编译时发生。 ideone.com/UUFOb7
  • 请注意,(+ 2 2) and 4` 只是不同的语法,其执行被推迟到运行时。我们可以在编译时将一个转换为另一个。

标签: optimization macros scheme lisp


【解决方案1】:

是的。 Macro hygiene 只是指宏扩展是否会意外捕获标识符。无论宏是否卫生,常规宏扩展(与阅读器宏扩展相反)发生在编译时。宏扩展用宏的执行结果替换宏的代码。它们的两个主要用例是转换语法(即 DSL),通过消除运行时计算来提高性能,或两者兼而有之。

想到几个例子:

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

您还可以结合示例并拥有一个可记忆的 SQL 解析器。几乎所有在编译时拥有所有必要输入并因此可以计算结果的场景都是候选方案。

【讨论】:

  • 我不认为将宏用于记忆是一种优化,因为(据我所知)它也可以用常规函数来实现;宏只是让它更好用
  • @CoderinoJavarino 宏生成的 memoization 实际上可以将 memoization 逻辑和函数体内联在一起,因此它不仅仅是将一个函数与另一个函数包装在一起。另外提供语法糖,例如选择哪些参数作为记忆的键,以及使用哪种相等等。
  • @CoderinoJavarino 函数记忆依赖于可变函数绑定:我们有一些函数 G,我们用一个引用旧 G 的新函数替换它。当旧 G 递归时,它调用根据需要记忆入口点。在具有不可变函数绑定的词法作用域下,我们不能做这种伎俩; G无论如何都会调用自己。宏可以为您生成一个记忆化的词法范围函数,同时看起来很像常规函数定义。
  • @CoderinoJavarino 作为 Kaz 所说的扩展,例如在 CL 中,函数 f 可以假定在其主体中对 f 的调用是调用自身,除非将 f 声明为 notinline(显然,这必须在编译之前发生)。所以这样的函数只能通过使用宏来记忆。 Mamoization-by-function 仅在 Python 等语言中有效,因为它具有非常基本的编译器。
【解决方案2】:

是的,卫生宏可以做这种事情。例如,这里有一个名为 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]))

【讨论】:

    猜你喜欢
    • 2022-01-14
    • 2010-09-07
    • 1970-01-01
    • 2013-10-14
    • 2023-04-02
    • 1970-01-01
    • 2012-07-14
    • 1970-01-01
    相关资源
    最近更新 更多