我正在尝试在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)信息。在扩展过程中,在编译过程中,编译器将*关联为对*#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))

我们将在这里使用一个宏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)信息与标识符相关联。在第一种情况下,当我们检查identifier-binding时,编译器会知道它与特定模块相关联(在本例中为#lang racket/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编码从x出来的broken-mod2时,事情发生了可怕的事情,因为语法对象引用的词法绑定(bind)到我们eval时还不存在。 eval是一个困难的野兽。

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

10-09 08:37