【问题标题】:Making a racket macro that generates a lambda制作一个生成 lambda 的球拍宏
【发布时间】:2019-04-29 06:04:22
【问题描述】:

我正在尝试制作一个小外壳来对 csv 文件执行类似 sql 的查询(出于好奇和尝试学习 Racket)。为此,我想用这种粗略的结构实现一个 select 宏(我计划让 x 成为 db 的列,但现在只传递了一行):

(define-syntax select
  (syntax-rules (* from where)
    ((_ col1 ... from db where condition)
     (filter (lambda (x) condition) <minutiae>))))

(其中细节是文件 IO 和管道代码)

x 的范围不是我想的那样:

x: undefined;
 cannot reference an identifier before its definition

我找到this example of a let-like macro

(define-syntax my-let*
  (syntax-rules ()
    ((_ ((binding expression) ...) body ...)
     (let ()
       (define binding expression) ...
       body ...))))

然后我继续尝试像这样生成 lambda:

(define-syntax my-lambda
  (syntax-rules ()
    ((_ body)
     (lambda (x)
       body))))

然后尝试模仿 let 示例的结构:

(define-syntax my-lambda
  (syntax-rules ()
    ((_ body)
     (lambda (x_)
       (let ()
         (define x x_)
         body)))))

这两个在调用((my-lambda (+ x 1)) 0)时给了我同样的错误:

x: undefined;
 cannot reference an identifier before its definition

根据我的阅读,这是由于卫生问题,但我似乎无法很好地掌握它来自己解决这个问题。我做错了什么,如何定义这些宏?为什么 let 示例有效,而 lambda 示例无效?

【问题讨论】:

    标签: lambda macros racket hygiene


    【解决方案1】:

    如您所料,问题在于卫生。

    let 示例有效,因为在给定正文中使用的标识符被传递给宏。

    但是,如果您尝试在正文中定义 x 标识符,而没有让编写正文的人真正明确知道它,那么您就是在破坏卫生(在范围)。

    您要创建的内容称为照应宏。 幸运的是,Racket 有你需要的东西。

    语法参数

    如果您以前使用过 Racket 参数,它的工作原理有点相同,但适用于宏。

    (define-syntax-parameter <x>
      (lambda (stx)
        (raise-syntax-error '<x> "Used outside select macro." stx)))
    

    这将定义一个名为&lt;x&gt; 的参数,您的宏用户将能够在您的select 宏中使用该参数。为了防止在外面使用,默认情况下,该参数被配置为引发语法错误。

    要定义可以使用它的唯一位置,请调用syntax-parameterize

    (define-syntax select
      (syntax-rules (* from where)
        [(_ col1 ... from db where condition)
         (findf
           (lambda (x)
             (syntax-parameterize ([<x> (make-rename-transformer #'x)])
               condition))
           <minutiae>)]))
    

    这将围绕condition 创建一个新范围,其中&lt;x&gt; 绑定到lambda 中的x

    然后你可以像这样调用你的宏:

    (select * from db where (eq? <x> 'foo))
    

    如果您尝试在宏之外使用 &lt;x&gt;,您将收到语法错误:

    > (displayln <x>)
    <x>: Used outside select macro.
      in: <x>
    

    完整代码

    #lang racket/base
    
    (require
      (for-syntax racket/base)
      racket/stxparam)
    
    (define-syntax-parameter <x>
      (lambda (stx)
        (raise-syntax-error '<x> "Used outside select macro." stx)))
    
    (define-syntax select
      (syntax-rules (* from where)
        [(_ col1 ... from db where condition)
         (findf
           (lambda (x)
             (syntax-parameterize ([<x> (make-rename-transformer #'x)])
               condition))
           db)]))
    
    (module+ test
      (require rackunit)
    
      (define db '(foo bar baz))
    
      (check-equal? (select * from db where (eq? <x> 'foo)) 'foo)
      (check-equal? (select * from db where (eq? <x> 'bar)) 'bar)
      (check-equal? (select * from db where (eq? <x> 'boop)) #f))
    

    【讨论】:

      【解决方案2】:

      这行得通:

      (define-syntax my-lambda
        (syntax-rules ()
          ((_ x body)
           (lambda (x) body))))
      
      (my-lambda x (+ x 1))
      

      虽然这不起作用:

      (define-syntax my-lambda*
        (syntax-rules ()
          ((_ body)
           (lambda (x) body))))
      
      (my-lambda* (+ x 1))
      

      可能有助于理解差异的一件事是,只要重命名一致,Racket 可以随意重命名变量(实际上在这里很难定义一致这个词,但直觉上它应该对您有意义)。球拍需要能够重命名以保持卫生。

      在第一种情况下,您调用(my-lambda x (+ x 1))。 Racket 可能会将其重命名为(my-lambda x$0 (+ x$0 1))。展开宏,我们得到(lambda (x$0) (+ x$0 1)),它是有效的。

      在第二种情况下,您调用(my-lambda* (+ x 1))。 Racket 可能会将其重命名为(my-lambda* (+ x$0 1))。展开宏,我们得到(lambda (x) (+ x$0 1)),所以x$0 是未绑定的。

      (注意我把事情简化了很多。实际上,Racket需要在第一种情况下扩展宏才能知道它需要同时重命名x。)

      要得到你想要的,你需要打破卫生习惯。这是一种简单的方法:

      (require syntax/parse/define)
      
      (define-simple-macro (my-lambda** body)
        #:with unhygiene-x (datum->syntax this-syntax 'x)
        (lambda (unhygiene-x) body))
      
      (my-lambda** (+ x 1))
      

      另请参阅https://stackoverflow.com/a/55899542/718349 和下面的评论,了解另一种破坏卫生的方法。

      【讨论】:

      • 请注意,此方法可能会在您的代码中插入细微的错误,因为x 被任意绑定在那里,并且将匹配正文中名为x 的任何变量,即使它们不应该匹配是相同的。使用syntax-parameter 感觉不那么hacky,并且可以防止这些错误。 (至少我是这么认为的,如果我错了,请纠正我 Sorawee)
      • syntax-parameter 确实比我的回答表现得更好,但不像你提到的那样。有关详细信息,请参阅scheme2011.ucombinator.org/papers/Barzilay2011.pdf 的第 1 部分。
      • 哦,我明白了。感谢您的链接!
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-04-09
      • 2011-08-02
      • 2012-07-04
      • 2012-01-07
      • 2022-01-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多