方案中语法对象的目的究竟是什么?

时间:2013-03-16 16:43:17

标签: scheme eval racket hygiene syntax-object

我正在尝试在python中编写一个类似于方案的小语言,以便更好地理解方案。

问题是我被困在语法对象上。我无法实现它们,因为我不太了解它们的用途以及它们的工作原理。

为了尝试理解它们,我在DrRacket中使用了语法对象。

从我能够找到的情况来看,评估#'(+ 2 3)与评估'(+ 2 3)没有什么不同,除非有一个词汇+变量遮蔽了(eval '(+ 2 3))变量。顶级命名空间,在这种情况下,5仍会返回(eval #'(+ 2 3)),但(define (top-sym) '(+ 2 3)) (define (top-stx) #'(+ 2 3)) (define (shadow-sym) (define + *) '(+ 2 3)) (define (shadow-stx) (define + *) #'(+ 2 3)) 只会抛出错误。

例如:

(eval (top-sym))

(eval (top-stx))(eval (shadow-sym))5都返回(eval (shadow-stx)),而6则会引发错误。 他们返回{{1}}。

如果我不知道更好,我会认为语法对象唯一特别之处(除了存储代码位置以获得更好的错误报告的微不足道的事实)是它们在某些情况下抛出错误他们的符号对应物会返回可能不需要的值的情况。

如果故事很简单,那么在常规列表和符号上使用语法对象就没有什么好处。

所以我的问题是:对于使它们如此特殊的语法对象我缺少什么?

1 个答案:

答案 0 :(得分:12)

语法对象是底层Racket编译器的词汇上下文的存储库。具体来说,当我们进入如下程序时:

#lang racket/base
(* 3 4)

编译器接收表示该程序的整个内容的语法对象。这是一个让我们看看语法对象是什么样子的例子:

#lang racket/base

(define example-program 
  (open-input-string
   "
    #lang racket/base
    (* 3 4)
   "))

(read-accept-reader #t)
(define thingy (read-syntax 'the-test-program example-program))
(print thingy) (newline)
(syntax? thingy)

请注意,程序中的*具有编译时表示作为thingy中的语法对象。目前,*thingy不知道它来自哪里:它还没有任何约束信息。在扩展的过程中,在编译期间,编译器将**的{​​{1}}相关联。

如果我们在编译时与事物进行交互,我们可以更容易地看到这一点。 (注意:我故意避免谈论#lang racket/base因为我想避免混淆关于编译时与运行时发生的事情的讨论。)

以下示例让我们更多地了解这些语法对象的作用:

eval

我们将在这里使用一个宏#lang racket/base (require (for-syntax racket/base)) ;; This macro is only meant to let us see what the compiler is dealing with ;; at compile time. (define-syntax (at-compile-time stx) (syntax-case stx () [(_ expr) (let () (define the-expr #'expr) (printf "I see the expression is: ~s\n" the-expr) ;; Ultimately, as a macro, we must return back a rewrite of ;; the input. Let's just return the expr: the-expr)])) (at-compile-time (* 3 4)) ,让我们在编译期间检查事物的状态。如果您在DrRacket中运行此程序,您将看到DrRacket首先编译该程序,然后运行它。在编译程序时,当它看到at-compile-time的使用时,编译器将调用我们的宏。

所以在编译时,我们会看到类似的东西:

at-compile-time

让我们稍微修改一下程序,看看我们是否可以检查标识符的identifier-binding

I see the expression is: #<syntax:20:17 (* 3 4)>

如果我们在DrRacket中运行此程序,我们将看到以下输出:

#lang racket/base
(require (for-syntax racket/base))

(define-syntax (at-compile-time stx)
  (syntax-case stx ()
    [(_ expr)
     (let ()
       (define the-expr #'expr)
       (printf "I see the expression is: ~s\n" the-expr)
       (when (identifier? the-expr)
         (printf "The identifier binding is: ~s\n" (identifier-binding the-expr)))

       the-expr)]))


((at-compile-time *) 3 4)

(let ([* +])
  ((at-compile-time *) 3 4))

(顺便说一下:为什么我们预先看到I see the expression is: #<syntax:21:18 *> The identifier binding is: (#<module-path-index> * #<module-path-index> * 0 0 0) I see the expression is: #<syntax:24:20 *> The identifier binding is: lexical 12 7 的输出?因为编译完全在运行之前完成!如果我们预编译程序并使用raco make保存字节码,我们 not 看到在运行程序时调用的编译器。)

当编译器达到at-compile-time的使用时,它知道将相应的词法绑定信息与标识符相关联。当我们在第一种情况下检查at-compile-time时,编译器知道它与特定模块相关联(在本例中为identifier-binding,这是#lang racket/base业务的内容。但在第二种情况下,它知道它是 lexical 绑定:编译器已经遍历module-path-index,因此它知道(let ([* +]) ...)的使用引用回绑定由*设置。

Racket编译器使用语法对象将这种绑定信息传递给客户端,例如我们的宏。


尝试使用let检查这类东西充满了问题:语法对象中的绑定信息可能不相关,因为在我们评估语法对象时,它们的绑定可能会引用事物那不存在!这基本上是你在实验中看到错误的原因。

仍然,这是一个显示s表达式和语法对象之间差异的例子:

eval

这个例子是精心构造的,因此语法对象的内容只引用模块绑定的东西,这些东西在我们使用#lang racket/base (module mod1 racket/base (provide x) (define x #'(* 3 4))) (module mod2 racket/base (define * +) ;; Override! (provide x) (define x #'(* 3 4))) ;;;;;;;;;;;;;;;;;;;;;;;;;;; (require (prefix-in m1: (submod "." mod1)) (prefix-in m2: (submod "." mod2))) (displayln m1:x) (displayln (syntax->datum m1:x)) (eval m1:x) (displayln m2:x) (displayln (syntax->datum m2:x)) (eval m2:x) 时就存在了。如果我们稍微改变一下这个例子,

eval

然后当我们尝试(module broken-mod2 racket/base (provide x) (define x (let ([* +]) #'(* 3 4)))) 来自eval的{​​{1}}时,事情会发生严重破坏,因为语法对象指的是当时不存在的词法绑定我们xbroken-mod2是一个困难的野兽。