【问题标题】:Difference between define and let w.r.t. syntax-rules keywordsdefine 和 let w.r.t. 的区别语法规则关键字
【发布时间】:2016-06-05 17:05:01
【问题描述】:

我是 Racket 和 Lisp 的初学者,正在玩语法定义。我定义了一个这样的简单转换:

(define-syntax hello
  (syntax-rules (in)
    ((_ name in world) (format "Hello ~a in ~a" name world))
    ((_ in name) (format "Hello ~a in here" name))))

现在,当我像这两种情况一样运行它时,它工作正常:

(hello "me" in "world") === "Hello me in world"

还有,

(define in "inside")
(hello me in in)  === "Hello me in inside"

但是,这会导致错误,

(let ([in "Shire"])
  (hello "Martin" in in)) === Error: hello: bad syntax in: (hello "Martin" in in)

那么,hello 在 let 绑定中失败的原因是什么,而对于定义却可以正常工作?另外,我在哪里可以获得有关此差异的更多具体信息?谢谢。

【问题讨论】:

  • 顺便说一句,我刚刚在 IRC 的#racket 上看到了你的问题。你绝对应该坚持超过 2 分钟。 IRC 主要是一种面向潜伏的媒体;如果您只发布实际问题(无需等待任何人回复“有人可以帮忙吗?”请求)然后等待 24 小时得到回复,您将获得最佳结果。 :-) (但是,正如你所知道的,我和 lexi-lambda 都能够在这里回答,所以这毕竟解决了。)

标签: macros scheme racket


【解决方案1】:

这与语法文字的工作方式有关。特别是,如果满足以下条件,则认为语法文字是匹配的:

  1. 文字在宏定义点没有绑定,在宏使用点也没有绑定。
  2. 文字在宏定义点有一个绑定,在宏使用点也有相同的绑定

define 案例对您有用,因为您可能将 define 放在与宏相同的模块中。这意味着条件 2 匹配:in 在宏定义和使用方面具有相同的绑定。但是,如果您在一个模块中定义了宏(没有define 用于in),并在另一个使用宏的模块中定义了in,事情就不会那么顺利了。 :-)

let 案例为in 创建了一个新绑定。这永远不会匹配顶级宏定义中in 的绑定。

【讨论】:

  • 因此,如果我理解正确,let 因标准 1 而失败,即in 的绑定在宏使用期间存在,但在定义宏时不存在。但是,我仍然不清楚define,因为在定义宏时这个绑定也不存在。我基本上在 Racket 的编辑器中定义了宏,运行它,然后在 REPL 中通过define 定义了in 绑定。因此,在这种情况下,第二个标准不应该匹配。还是我错过了什么?
  • 而且,当有一个全局 define 时也会发生这种情况。在函数中包含 (define n "something") 然后调用 (hello "ok" in in) 也会导致相同的错误。
  • 正如亚历克西斯的回答所解释的那样,模块级defines 会影响模块中的所有内容(“相互递归”是她使用的措辞,但仍然是相同的含义),而不仅仅是后面的内容。至于函数中的内部definethose are transformed into letrec 和模块级define 的处理方式不同。
  • 正确,我在阅读亚历克西斯的答案后重新阅读了您的答案,这是有道理的。谢谢。
【解决方案2】:

这样做的原因有点微妙——“不应该”工作,但当define 位于顶层时,它实际上改变syntax-rules 的解释,即行为出现的地方。

如果您查看 the documentation for syntax-rules,您会发现每个 literal-id 的处理方式与 syntax-case 中的相同。文档指出,文字是使用 free-identifier=? 进行比较的:

literal-id 具有相同绑定的 id 匹配语法对象,该语法对象是具有相同绑定的标识符 free-identifier=? .

这意味着文字作为绑定进行匹配,而不是作为基准。也就是说,如果您在 let 中有一个 in 的本地定义,它是与在 syntax-rules 中标记为文字的 in 的单独绑定,因此它们将不匹配。此外,在单独的模块中使用define 引入的标识符也将不匹配。

有趣的是,如果 in 从定义宏的模块导出,然后使用 rename-in 导入,重命名的绑定仍然有效,因为 Racket 会跟踪重命名并认为它们是相同的绑定 即使它们不是同名。这些是正交的概念,即使有时很容易忽略该想法的所有细微差别,但它在某种程度上是卫生所固有的。

回到有问题的例子,为什么下面的工作?

(define-syntax hello
  (syntax-rules (in)
    ((_ name in world) (format "Hello ~a in ~a" name world))
    ((_ in name) (format "Hello ~a in here" name))))

(define in "inside")
(hello "me" in in)

好吧,定义上下文是相互递归的,所以在宏定义之后的in的定义实际上是由syntax-rules拾取的。因此,hello 宏实际上需要与in 相同的绑定,因此调用有效。但是,如果 hello 是在与 in 不同的模块中定义的,则宏调用将不起作用,因为绑定会不同:

(module hello racket
  (provide hello)
  (define-syntax hello
    (syntax-rules (in)
      ((_ name in world) (format "Hello ~a in ~a" name world))
      ((_ in name) (format "Hello ~a in here" name)))))

(require 'hello)

(define in "inside")
(hello "me" in in) ; => hello: bad syntax in: (hello "me" in in)

【讨论】:

  • 这就解释了。谢谢!
猜你喜欢
  • 2020-07-24
  • 2017-08-18
  • 2015-10-18
  • 2014-05-26
  • 2012-02-27
  • 2013-10-04
  • 2018-12-09
  • 2017-07-02
  • 2018-09-15
相关资源
最近更新 更多