語法對象是底層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
中的*
不知道它來自哪裏:它尚無綁定信息。在編譯過程中,擴展爲,編譯器將*
作爲*
的#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
用途時,它知道將適當的詞彙綁定信息與標識符相關聯。當我們在第一種情況下檢查identifier-binding
時,編譯器知道它與一個特定模塊相關聯(在這種情況下,#lang racket/base
,這是業務所關注的)。但在第二種情況下,它知道這是一個詞法綁定:編譯器已經走過了(let ([* +]) ...)
,因此它知道*
的使用引用回到由let
設置的綁定。
Racket編譯器使用語法對象將這種綁定信息傳遞給客戶端,比如我們的宏。
嘗試使用eval
檢查這種東西是充滿了問題:在語法對象的綁定信息可能是不相關的,因爲當時我們評估的語法對象,他們的綁定可能是指事那不存在!這基本上是你在實驗中看到錯誤的原因。
儘管如此,這裏是一個例子,顯示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)
這個例子是精心構造使得語法對象的內容僅指模塊結合的東西,這在我們使用eval
時會存在。如果我們稍微改變的例子,
(module broken-mod2 racket/base
(provide x)
(define x
(let ([* +])
#'(* 3 4))))
那麼事情打破可怕的,當我們試圖eval
所散發出來的broken-mod2
的x
,因爲語法對象指的是詞彙的結合不被存在我們的時間是eval
。 eval
是一個困難的野獸。
你看過嗎:http://docs.racket-lang.org/reference/syntax-model.html#%28part._stxobj-model%29沒有? – dyoo 2013-03-16 18:05:47
@dyoo我已經讀了好幾遍,這是讓我覺得我理解他們的原因,直到我嘗試了上述測試。從那個頁面,我會認爲'(eval(shadow-stx))'應該返回'6'。 – Matt 2013-03-16 19:33:45
只是爲了澄清,當你詢問語法對象時,這實際上是一個獨特的球拍事情:它不是一般的計劃。語法對象是一種數據類型,它是Racket語言基礎結構的核心。 – dyoo 2013-03-16 23:19:53