2013-03-16 75 views
5

我想在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

如果我不知道更好的話,我會認爲語法對象的唯一特別之處(除了它們存儲代碼的位置以獲得更好的錯誤報告的簡單事實)之外,他們會拋出一個錯誤某些情況下,他們的符號對應物會返回可能不需要的價值。

如果故事很簡單,那麼在常規列表和符號上使用語法對象將沒有真正的優勢。

所以我的問題是:我錯過了什麼使它們如此特別的語法對象?

+0

你看過嗎:http://docs.racket-lang.org/reference/syntax-model.html#%28part._stxobj-model%29沒有? – dyoo 2013-03-16 18:05:47

+0

@dyoo我已經讀了好幾遍,這是讓我覺得我理解他們的原因,直到我嘗試了上述測試。從那個頁面,我會認爲'(eval(shadow-stx))'應該返回'6'。 – Matt 2013-03-16 19:33:45

+2

只是爲了澄清,當你詢問語法對象時,這實際上是一個獨特的球拍事情:它不是一般的計劃。語法對象是一種數據類型,它是Racket語言基礎結構的核心。 – dyoo 2013-03-16 23:19:53

回答

11

語法對象是底層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-mod2x,因爲語法對象指的是詞彙的結合不被存在我們的時間是evaleval是一個困難的野獸。

+0

順便說一下,馬修·弗拉特將在Clojure/West 2013上專門針對這個話題發表演講。他的演講http://clojurewest.org/sessions#flatt將有更多的例子可以幫助澄清。 – dyoo 2013-03-16 22:42:56

+0

謝謝!談話是否可以在線觀看? – Matt 2013-03-17 16:45:08

+0

@Matt:我相信如此,但我並不完全確定。 Clojure/West 2012確實有一個由InfoQ http://clojurewest.org/news/2012/5/11/clojurewest-video-schedule.html主辦的視頻檔案,所以我預計他們今年也會這樣做。 – dyoo 2013-03-18 00:20:02