我正在尝试在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/