構文オブジェクトは、基礎となる 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
はそれがどこから来たのかわかりません。拘束力のある情報はまだありません。コンパイラがofへの参照として関連付けるのは、展開のプロセス中、コンパイル中です。*
*
#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
、そのmodule-path-index
ビジネスの対象となる )。しかし、2 番目のケースでは、それがレキシカルバインディングであることを認識しています。コンパイラはすでに を調べており(let ([* +]) ...)
、 の使用が*
によって設定されたバインディングに戻っていることを認識していlet
ます。
Racket コンパイラーは構文オブジェクトを使用して、マクロなどのクライアントにその種のバインディング情報を伝えます。
eval
この種のものを検査するために使用しようとすると、問題が発生します: 構文オブジェクトのバインディング情報が適切でない可能性があります。これは、構文オブジェクトを評価するまでに、それらのバインディングが存在しないものを参照している可能性があるためです! これが基本的に、実験でエラーが発生した理由です。
それでも、s 式と構文オブジェクトの違いを示す 1 つの例を次に示します。
#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
、事態はひどく壊れます。 難しい獣です。x
broken-mod2
eval
eval