scheme - 方案中语法对象的确切用途是什么?

标签 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

如果我不太了解的话,我会认为语法对象的唯一特殊之处(除了它们存储代码位置以更好地报告错误的小事实之外)是它们在某些情况下抛出错误在这种情况下,它们的符号对应项将返回可能不需要的值。

如果故事这么简单,那么使用语法对象相对于常规列表和符号就没有真正的优势。

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

最佳答案

语法对象是底层 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 中的 * 不知道它来自哪里:它还没有绑定(bind)信息。正是在编译的扩展过程中,编译器将*关联为对#langracket/的*的引用基础

如果我们在编译时与事物交互,我们可以更容易地看到这一点。 (注意:我故意避免谈论 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))

我们将在这里使用一个宏,at-compile-time,让我们在编译期间检查事物的状态。如果你在DrRacket中运行这个程序,你会看到DrRacket首先编译该程序,然后运行它。当它编译程序时,当它看到at-compile-time的使用时,编译器将调用我们的宏。

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

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

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

#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))

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

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

(顺便说一句:为什么我们会在前面看到 at-compile-time 的输出?因为编译完全在运行时之前完成!如果我们预编译程序并保存字节码使用 raco make ,我们在运行程序时不会看到编译器被调用。)

当编译器使用at-compile-time时,它知道将适当的词法绑定(bind)信息与标识符相关联。当我们在第一种情况下检查标识符绑定(bind)时,编译器知道它与特定模块关联(在本例中为#langracket/base,这就是module-path-index 业务是这样的)。但在第二种情况下,它知道这是一个词法绑定(bind):编译器已经遍历了(let ([* +]) ...),所以它知道 * 的使用引用了 let 设置的绑定(bind)。

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

<小时/>

尝试使用 eval 来检查此类内容充满了问题:语法对象中的绑定(bind)信息可能不相关,因为当我们评估语法对象时,它们的绑定(bind)可能会发生变化。指不存在的事物!这就是您在实验中看到错误的根本原因。

不过,这里有一个示例,显示了 s-表达式和语法对象之间的区别:

#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)

这个示例经过精心构建,以便语法对象的内容仅引用模块绑定(bind)的内容,这些内容将在我们使用 eval 时存在。如果我们稍微改变一下这个例子,

(module broken-mod2 racket/base
  (provide x)
  (define x  
    (let ([* +])
      #'(* 3 4))))

然后,当我们尝试eval来自broken-mod2x时,事情会严重崩溃,因为语法对象引用的是当我们eval时,词法绑定(bind)还不存在。 eval 是一头困难的野兽。

关于scheme - 方案中语法对象的确切用途是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15451852/

相关文章:

scheme - 如何在Scheme中将列表分成两部分

lambda - lambda 表达式中的 Racket 和未绑定(bind)标识符,与 r5rs 对比

scheme - 需要 Racket 中的模块未提供的标识符

scheme - Memoization performance - SICP exercise 3.27 似乎是错误的

scheme - csi 和 csc(Chicken Scheme)的区别

javascript - 使用 JavaScript eval 解析 JSON

perl - perl eval "...propagated"记录在哪里

string - 如何评估 Access 查询中另一个字段中包含的字段名称?

scheme - DrRacket 中的 foldr 和 Foldl

lisp - Lisp 和 Scheme 中的 WebSockets 库?