【问题标题】:What is the difference between define vs define-syntax and let vs let-syntax when value is syntax-rules?当值是语法规则时,define vs define-syntax 和 let vs let-syntax 有什么区别?
【发布时间】:2020-07-24 13:50:20
【问题描述】:

我正在我的 Scheme 实现中实现 Hygienic 宏,我刚刚实现了 syntax-rules,但我有这个代码:

(define odd?
  (syntax-rules ()
    ((_ x) (not (even? x)))))

that 和 this 应该有什么区别:

(define-syntax odd?
  (syntax-rules ()
    ((_ x) (not (even? x)))))

据我了解syntax-rules 只是返回语法转换器,为什么不能只使用define 将其分配给符号?为什么我需要使用define-syntax?这个表达式有什么额外的作用?

应该首先在方案中工作吗?还是只有第二个?

letlet-syntaxletrecletrec-syntax 之间还有什么区别。 (define|let|letrec)-syntax 是否应该只检查值是否是语法转换器?

编辑

我有这个实现,仍然使用 lisp 宏:

;; -----------------------------------------------------------------------------
(define-macro (let-syntax vars . body)
  `(let ,vars
     ,@(map (lambda (rule)
              `(typecheck "let-syntax" ,(car rule) "syntax"))
            vars)
     ,@body))

;; -----------------------------------------------------------------------------
(define-macro (letrec-syntax vars . body)
  `(letrec ,vars
     ,@(map (lambda (rule)
              `(typecheck "letrec-syntax" ,(car rule) "syntax"))
            vars)
     ,@body))

;; -----------------------------------------------------------------------------
(define-macro (define-syntax name expr)
  (let ((expr-name (gensym)))
    `(define ,name
       (let ((,expr-name ,expr))
         (typecheck "define-syntax" ,expr-name "syntax")
         ,expr-name))))

这段代码正确吗?

这段代码应该有效吗?

(let ((let (lambda (x) x)))
  (let-syntax ((odd? (syntax-rules ()
                        ((_ x) (not (even? x))))))
     (odd? 11)))

【问题讨论】:

    标签: macros scheme


    【解决方案1】:

    这个问题似乎暗示了对宏的一些深刻困惑。

    让我们想象一种语言,其中syntax-rules 返回一些语法转换器函数(我不确定这在 RnRS 方案中是否一定是真的,我认为在 Racket 中也是如此),而 letlet-syntax 是一样。

    所以我们来写这个函数:

    (define (f v)
      (let ([g v])
        (g e (i 10)
           (if (= i 0)
               i
               (e (- i 1))))))
    

    我们当然可以变成这样:

    (define (f v n)
      (v e (i n)
         (if (<= i 0)
             i
             (e (- i 1)))))
    

    另外我会告诉你,环境中没有ei的绑定。

    解释器对这个定义的意义是什么?它可以编译吗?它是否可以安全地推断出i 不可能有任何意义,因为它被用作函数然后用作数字?它可以安全地做任何事情吗?

    答案是不,它不能。在它知道函数的参数是什么之前,它什么也做不了。这意味着每次调用f 时,它都必须再次做出该决定。特别是,v 可能是:

    (syntax-rules ()
      [(_ name (var init) form ...)
       (letrec ([name (λ (var)
                        form ...)])
         (name init))]))
    

    在此之下f 的定义确实有某种意义。

    事情变得更糟:更糟。这个怎么样?

    (define (f v1 v2 n)
      (let ([v v1])
        (v e (i n)
           ...
           (set! v (if (eq? v v1) v2 v1))
           ...)))
    

    这意味着像这样的系统不会知道它要解释的代码是什么意思,直到它解释它的那一刻,甚至在那之后,你可以从上面的第二个函数看。

    因此,Lisps 并没有出现这种可怕的情况,而是做了一些理智的事情:他们将评估代码位的过程分成阶段,从概念上讲,每个阶段发生在下一个阶段之前。

    这是一些想象中的 Lisp 的序列(这有点接近 CL 所做的,因为我的大部分知识都是关于此的,但它并不打算代表任何特定的系统):

    1. 有一个阶段,代码从某个字符序列转换为某个对象,可能借助用户定义的代码;
    2. 有一个阶段,该对象被用户和系统定义的代码(宏)重写为其他对象——这个阶段的结果是用函数和少量原始特殊事物表示的东西,传统上称为“特殊形式”,为阶段 3 和 4 的过程所熟知;
    3. 可能存在编译阶段 2 中的对象的阶段,并且该阶段可能涉及另一组用户定义的宏(编译器宏);
    4. 有一个评估结果代码的阶段。

    对于每个代码单元,这些阶段按顺序发生每个阶段在下一个阶段开始之前完成

    这意味着用户可以干预的每个阶段都需要自己的一组定义和绑定形式:例如,需要可以说“这件事控制了阶段 2 发生的事情”。

    这就是define-syntaxlet-syntax &c 所做的:他们说“这些绑定和定义控制了第 2 阶段发生的事情”。例如,您不能使用definelet 来做到这一点,因为在阶段2,这些操作还没有意义:它们获得了意义(可能它们本身就是宏仅在第 3 阶段扩展为一些原始事物)。在第 2 阶段,它们只是宏正在摄取和吐出的一些语法。

    【讨论】:

    • 感谢您的回答,我正在实施 Scheme 宏,我刚刚得出结论,在 eval 之前我需要多做一步。我已经有 Lisp 宏,但它们与 CL 不同,它们在运行时由 eval 调用,一些宏是用 JavaScript 编写的,不会扩展(您可以将它们视为特殊形式)但它们与宏是相同类型的对象.你知道任何例子,你会看到 LISP 宏需要在运行时扩展,如果它们像函数一样扩展但有点双重 eval 会破坏系统?
    • 这里是我的解释器codepen.io/jcubic/pen/GxgWBq 的开发代码,它有一种双重评估的定义宏,你能创建它中断的例子吗?它也有语法规则,但它不能 100% 正确工作,因为它们与宏同时扩展,因此它在 R5RS 的示例中中断。我问这个是因为我不想改变 lisp 宏的工作方式,我只想要语法规则宏的 2 阶段。
    • @jcubic:我只能评论普通 lisp 实现的作用。
    猜你喜欢
    • 2018-09-15
    • 2020-07-24
    • 1970-01-01
    • 2016-01-11
    • 2022-12-14
    • 2013-08-08
    • 2010-09-12
    • 2013-09-22
    • 1970-01-01
    相关资源
    最近更新 更多