6

スキームをよりよく理解するために、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))すべて return 5、 while(eval (shadow-stx))はエラーをスローします。それらのどれも戻りません6

私がよく知らなかった場合、構文オブジェクトの唯一の特別な点は (エラー報告を改善するためにコードの場所を保存するという些細な事実を除けば)、特定の状況でエラーをスローすることだけだと思います。対応するシンボルは、望ましくない可能性のある値を返します。

ストーリーがそれほど単純である場合、通常のリストやシンボルよりも構文オブジェクトを使用する利点はありません。

だから私の質問は: 構文オブジェクトを特別なものにするために欠けているものは何ですか?

4

1 に答える 1

12

構文オブジェクトは、基礎となる 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、事態はひどく壊れます。 難しい獣です。xbroken-mod2evaleval

于 2013-03-16T22:35:59.727 に答える