【问题标题】:What is the difference between these macros?这些宏有什么区别?
【发布时间】:2018-01-08 18:57:34
【问题描述】:

我对宏在 Scheme 中的工作方式(特别是在 Chicken Scheme 中)有一些疑问,让我们考虑这个例子:

(define (when-a condition . body)
  (eval `(if ,condition
     (begin ,@body)
     '())))

(define-syntax when-b
  (er-macro-transformer
    (lambda (exp rename compare)
      (let ((condition (cadr exp))
            (body (cddr exp)))
        `(if ,condition (begin ,@body) '())))))

(define-syntax when-c
  (ir-macro-transformer
    (lambda (exp inject compare)
      (let ((condition (cadr exp))
            (body (cddr exp)))
        `(if ,condition (begin ,@body) '())))))

(define-syntax when-d
  (syntax-rules ()
    ((when condition body ...)
     (if condition (begin body ...) '()))))
  1. 我可以将when-a 视为一个宏吗?我觉得我不能严格地认为它是一个宏,因为我没有使用define-syntax,但我不能说出任何不喜欢这种实现的实际理由。

  2. 我的宏是否卫生?

  3. when-bwhen-c 之间有什么区别吗?由于我没有使用renameinject,我认为没有。

【问题讨论】:

    标签: macros scheme chicken-scheme


    【解决方案1】:

    我可以考虑when-a宏吗?我觉得我不能严格地认为它是一个宏,因为我没有使用定义语法,但我不能说出任何不喜欢这种实现的实际理由。

    这类似于宏,但与真正的宏并不完全相同,原因如下:

    • 真正的宏与基于 eval 的“宏”之间的主要区别在于,您的方法将在调用它之前评估其所有参数。这是一个非常重要的区别。例如:(if #f (error "oops") '()) 将评估为 '()(when-a #f (error "oops")) 将引发错误。
    • 不卫生。例如,事先可能已经做了类似(eval '(define if "not a procedure")) 之类的事情,这意味着这个评估会失败; “扩展”的主体表达式中的if 不引用定义站点上的if
    • 它不会在编译时展开。这是使用宏的另一个主要原因。编译器将展开它,并且在运行时不会执行任何计算来执行展开。宏本身将完全消失。只剩下扩展了。

    我的宏是否卫生?

    只有when-cwhen-d 是,因为ir-macro-transformersyntax-rules 做出的保证。在 when-b 中,您必须重命名 ifbegin 以使它们在宏定义站点上引用 ifbegin 的版本。

    例子:

    (let ((if #f))
      (when-b #t (print "Yeah, ok")))
    
    == expands to ==>
    
    (let ((if1 #f))
      (if1 #t (begin1 (print "Yeah, ok"))))
    

    这会失败,因为if 的两个版本(这里用一个额外的1 后缀注释)指的是同一个东西,所以我们最终会在操作员位置调用#f

    相比之下,

    (let ((if #f))
      (when-c #t (print "Yeah, ok")))
    
    == expands to ==>
    
    (let ((if1 #f))
      (if2 #t (begin1 (print "Yeah, ok"))))
    

    这将按预期工作。如果你想改写when-b更卫生,可以这样:

    (define-syntax when-b
      (er-macro-transformer
        (lambda (exp rename compare)
          (let ((condition (cadr exp))
                (body (cddr exp))
                (%if (rename 'if))
                (%begin (rename 'begin)))
            `(,%if ,condition (,%begin ,@body) '())))))
    

    注意额外的以% 为前缀的标识符,它们引用ifbegin 的原始值,因为它们位于宏的定义位置。

    when-b 和 when-c 有什么区别吗?由于我没有使用重命名或注入,我认为没有。

    有。 隐式重命名宏之所以被称为,是因为它们隐式重命名来自使用站点的所有标识符,以及您在正文中引入的每个新标识符。如果您注入任何标识符,则会撤消此隐式重命名,这会使它们不卫生地可供调用代码捕获。

    另一方面,显式重命名宏被调用,因为您必须显式重命名任何标识符以防止它们被调用代码捕获。

    【讨论】:

    • 谢谢,最后一件事:使用Chicken,如何部分编译代码以查看扩展的宏?有办法吗?
    • 您可以在解释器中使用,x。还有expand-full egg,但它很糟糕(它不能正确维护宏环境,所以它不能很好地处理let-syntax 等)。最后,许多程序员使用的一个非常实用的技巧是扩展为带引号的列表(只需在输出前添加'),这样您就可以看到扩展的样子。
    猜你喜欢
    • 2020-06-28
    • 1970-01-01
    • 1970-01-01
    • 2013-01-16
    • 2018-08-25
    • 2012-09-27
    • 2011-11-22
    • 2015-08-08
    相关资源
    最近更新 更多