[Contents]   [Back]   [Prev]   [Up]   [Next]   [Forward]  


5. 式

式の型はプリミティブか、あるいはそれから導出されたものとして分類される。プリミティブ式の種類には、変数と手続き呼び出しが含まれる。導出式型は文法的にはプリミティブではないが、マクロとして定義することができる。マクロ定義が複雑となるバッククォート式を除いて、導出式型はライブラリ機能に分類される。この適切な定義については、section 8.3 導出式型に示す。

5.1 プリミティブ式型

5.1.1 変数の参照

[[構文]] <変数>

変数(section 4.1 変数、構文キーワード、領域)で構成される式は変数を参照する。変数の値は、変数がバインドされた記憶域に保管された値である。バインドされていない変数を参照した場合はエラーである。

  (define x 28)
  x  => 28

5.1.2 リテラル式

[[構文] (quote <データ>)
[[構文] '<データ>
[[構文] <定数>

(quote <データ>)は<データ>に評価される。<データ>は、Schemeオブジェクトのいかなる外部表現であってもよい(section 4.3 外部表現参照)。この記法は、書かれた通りの定数をSchemeコードに含めるのに使用される。

  (quote a)                     =>  a
  (quote #(a b c))              =>  #(a b c)
  (quote (+ 1 2))               =>  (+ 1 2)

(quote <データ>)は、'<データ>と略記してもよい。この二つの記法はすべての点で等価である。

  'a                   =>  a
  '#(a b c)            =>  #(a b c)
  '()                  =>  ()
  '(+ 1 2)             =>  (+ 1 2)
  '(quote a)           =>  (quote a)
  ''a                  =>  (quote a)

数値定数、文字列定数、文字定数、論理定数はそれ自身に評価されるので、クォートをつける必要はない。

  '"abc"     =>  "abc"
  "abc"      =>  "abc"
  '145932    =>  145932
  145932     =>  145932
  '#t        =>  #t
  #t         =>  #t

section 4.4 記憶モデルで言及したように、set-car!string-set!のような書き換え手続きを使用して、定数(リテラル式の値)を変更することはエラーである。

5.1.3 手続き呼び出し

[[構文]] (<オペレータ> <オペランド1> ...)

手続きの呼び出しは、呼び出す手続きの式とそれに渡す引数を小括弧で括って表記される。オペレータの式とオペランドの式は(不定の順序で)評価され、評価結果の手続きに評価結果の引数が渡される。

  (+ 3 4)                   =>  7
  ((if #f + *) 3 4)         =>  12

初期環境では、変数の値として一定数の手続きが利用できる。例えば上記の例の加算手続きと乗算手続きは、それぞれ変数+*の値である。新しい手続きは、ラムダ式(section 5.1.4 手続き参照)を評価して生成される。

手続き呼び出しはいかなる値でも返すことができる(section 7.4 制御機能values参照)。valuesの場合を除いて初期環境で利用できる手続き呼び出しは1つの値を返すか、さもなければapplyのような手続きの場合には、引数の一つの呼び出しから返される値を次の手続きに渡す。

手続きの呼び出しはコンビネーションとも呼ばれる。

注: その他のLisp方言とは対照的に評価の順序は不定であり、オペレータ式とオペランド式は、必ず同一評価規則で評価される。

注: 一般には評価の順序は不定であるが、オペレータ式とオペランド式のいかなる同時並行的評価の結果も、一定の順次的評価と一致しなければならない。評価の順序は手続きの呼び出しごとに選択できる。

注: 多くのLisp方言では空のコンビネーション()は有効な式である。Schemeにおいてはコンビネーションは少くとも一つの下位の式を持っていなければならず、したがって()は文法上有効な式ではない。

5.1.4 手続き

[[構文]] (lambda <仮引数> <ボディ>)

構文: <仮引数>は下記に示す通りの仮引数のリスト(13)でなければならず、<ボディ>には一つ以上の式が連続しなければならない。

意味: ラムダ式は手続きに評価される。ラムダ式が評価された時に有効な環境が、手続きの一部として記憶される。後で手続きが何らかの実引数とともに呼び出された時には、仮引数リスト内の変数が新しい記憶域にバインドされてラムダ式が評価された環境が拡張され、対応する実引数の値がその記憶域に保管される。ラムダ式のボディの中の式は、その拡張された環境で順次的に評価される。手続き呼び出しの結果として、ボディの最後の式の結果が返される。

  (lambda (x) (+ x x))     =>   ;手続き
   ((lambda (x) (+ x x))4) => 8 ;(14)
   (define reverse-subtract 
    (lambda (x y) (- y x)))
   (reverse-subtract 7 10) => 3 ;(15)
   (define add4
    (let ((x 4))
      (lambda (y) (+ x y))))
  (add4 6) => 10

<仮引数群>は次の形式でなければならない。

<仮引数群>の中に<変数>が二度以上現れた場合はエラーである。

  ((lambda x x) 3 4 5 6) => (3 4 5 6);(16)
  ((lambda(x y . z) z) 
    3 4 5 6)         => (5 6);(17)

ラムダ式の評価の結果生成された手続き一つ一つには、eqv?eq?が手続きに対して有効になるように(section 7.1 同値を調べる述語関数参照)、(概念的には)保管場所を示す標識が付けられる。

5.1.5 条件式

[[構文]] (if <テスト> <帰結> <代わりの帰結>)
[[構文]] (if <テスト> <帰結>)

構文: <テスト>、<帰結>、<代わりの帰結>は、任意の式であってよい。

意味: if式は次のように評価される。最初に<テスト>が評価される。その結果の値が真であれば(section 7.3.1 論理式参照)、<帰結>が評価されてその値が返される。さもなければ<代わりの帰結>が評価されてその値が返される。<テスト>の結果の値が偽で<代わりの帰結>が指定されていない場合は、式の結果は不定である。

  (if (> 3 2) 'yes 'no) => yes
  (if (> 2 3) 'yes 'no) => no
  (if (> 3 2)
      (- 3 2)
      (+ 3 2)) => 1

5.1.6 割り当て

[[構文]] (set! <変数> <式>)

<式>が評価されて、<変数>がバインドされている記憶位置に結果の値が保管される。<変数>は、set!式を取り囲む領域内かトップレベルの、いずれかにバインドしなければならない。set!式の結果は不定である。

  (define x 2)
  (+ x 1)     => 3
  (set! x 4)  => unspecified
  (+ x 1)     => 5

5.2 導出式型

section 5.3 マクロに説明する通り、本節の構成手続きは名前の衝突を起こさないものである(ハイジーニック)。参考のために、本節に説明する構成手続きのほとんどを前節に説明したプリミティブ構成手続きに変換するマクロ定義を、section 8.3 導出式型に示す。

5.2.1 条件節

[[ライブラリ構文]] (cond <節1> <節2> ...)

構文: <節>の一つ一つは、

  (<テスト> <式1> ...)

の形式でなければならない。ここに <テスト>はあらゆる式である。

もう1つの書法として、<節>を次のように書くこともできる。

  (<テスト> => <式>)

最後の <節>はelse節であってもよく、これは

  (else <式1> <式2> ...)

の形式である。

意味: cond式は、連続する<節>の<テスト>式を順に評価しながら、そのうちの一つが真値(section 7.3.1 論理式参照)に評価されるまで評価が継続される。<テスト>が真値に評価されると、その<節>の残りの<式>が順次評価される。その<節>の最後の<式>の結果がcond式全体の結果として返される。選択した<節>に<テスト>のみが存在して<式>が存在しない場合、<テスト>の値が結果として返される。選択した<節>がもう1つの書法=>形式を使用している場合、続く<式>が評価される。<式>の値は引数を1つとる手続きでなければならない。この場合、<テスト>の値に基づいてこの手続きが呼び出され、手続きの返す値がcond式の返す値になる。すべての<テスト>が偽値に評価されかつelse節が存在しなかった場合、その条件式の結果は不定である。else節が存在する場合はその <式>が評価されて、最後の式の値が返される。

  (cond ((> 3 2) 'greater)
        ((< 3 2) 'less))                    =>  greater

  (cond ((> 3 3) 'greater)
        ((< 3 3) 'less)
        (else 'equal))                      =>  equal

  (cond ((assv 'b '((a 1) (b 2))) => cadr)
        (else #f))                          =>  2

[[ライブラリ構文]] (case <キー> <節1> <節2> ...)

<キー>はどんな式でもよい。<節>の一つ一つは次の形式でなければならない。

    ((<データ1> ...) <式1> <式2> ...) 

ここに、<データ>の一つ一つはオブジェクトの外部表現である。<データ>はすべて、異なるものでなければならない。最後の<節>は"else節"であってもよくこれは次の形式をとる。

  (else <式1> <式2> ...).

意味: case式は次のように評価される。<キー>が評価されて、その結果が<データ>の一つ一つと比較される。評価中の<キー>が(eqv?の意味で、section 7.1 同値を調べる述語関数参照)<データ>に等しい場合、対応する<節>の式が左から右へと評価され、<節>の最後の式の結果がcase式の結果として返される。評価中の<キー>の結果がすべての<データ>と異なる場合、else節が存在すればその式群が評価されて、その最後の式の結果がcase式の結果となる。else節が存在しない場合は、case式の結果は不定である。

  (case (* 2 3)
    ((2 3 5 7) 'prime)
    ((1 4 6 8 9) 'composite))     =>  composite
  (case (car '(c d))
    ((a) 'a)
    ((b) 'b))                     =>  unspecified
  (case (car '(c d))
    ((a e i o u) 'vowel)
    ((w y) 'semivowel)
    (else 'consonant))            =>  consonant
[[ライブラリ構文]] (and <テスト1> ...)

<テスト>は左から右へと評価され、偽値(section 7.3.1 論理式参照)に評価された最初の式の値が返される。残りの式は全く評価されない。すべての式が真値に評価された場合は、最後の式の値が返される。式が存在しない場合は#tが返される。

  (and (= 2 2) (> 2 1))           =>  #t
  (and (= 2 2) (< 2 1))           =>  #f
  (and 1 2 'c '(f g))             =>  (f g)
  (and)                           =>  #t
[[ライブラリ構文]] (or <テスト1> ...)

<テスト>式が左から右へと評価され、真値(section 7.3.1 論理式参照)に評価された最初の式の値が返される。残りの式は一切評価されない。すべての式が偽値に評価された場合は、最後の式の値が返される。式が存在しない場合は、#fが返される。

  (or (= 2 2) (> 2 1))            =>  #t
  (or (= 2 2) (< 2 1))            =>  #t
  (or #f #f #f)                   =>  #f
  (or (memq 'b '(a b c))
      (/ 3 0))                    =>  (b c)

5.2.2 バインディング構成手続き

letlet*letrecの三つのバインディング構成手続きを使用すれば、Algol 60同様のブロック構造をSchemeで実現できる。この三つの構成手続きの構文は同一であるが、変数バインディングを作成する領域が異なっている。let式では、変数のバインディングが行なわれる以前に、初期値が計算される。let*式では、バインディングと評価が順次的に行なわれる。一方letrec式では、初期値が計算されている間にもバインディングが有効であり、したがって相互に再帰的な定義が可能である。

[[ライブラリ構文]] (let <バインディング> <ボディ>)

構文: <バインディング>は次の形式でなければならない

    ((<変数1> <初期値1>) ...)

ここに<初期値>は式である。さらに<ボディ>には一つ以上の式が連続しなければならない。バインドしている変数リスト内に<変数>が二度以上現れた場合はエラーである。

意味: 初期値は現行の環境で(不定の順序で)評価される。<変数>は結果を保持する新しい記憶域にバインドされる。<ボディ>はその拡張された環境で評価され、<ボディ>の最後の式の値が返される。<変数>のバインディング一つ一つは、ボディをその領域とする。

  (let ((x 2) (y 3))
    (* x y))                      =>  6

  (let ((x 2) (y 3))
    (let ((x 7)
          (z (+ x y)))
      (* z x)))                   =>  35

名前付let、section 5.2.4 繰り返しも参照。

[[ライブラリ構文]] (let* <バインディング> <ボディ>)

構文: <バインディング>は次の形式でなければならない。

    ((<変数1> <初期値1>) ...)

さらに、<ボディ>には一つ以上の式が連続しなければならない。

意味: let*letに似ているが、バインディングは左から右に順次行なわれ、(<変数> <初期値>)が示すバインディングの領域は、let*式からバインディングの右側へ向かう領域である。したがって第2のバインディングが行なわれる環境では最初のバインディングが見え、以後のバインディングでも同様である。

  (let ((x 2) (y 3))
    (let* ((x 7)
           (z (+ x y)))
      (* z x)))             =>  70
[[ライブラリ構文]] (letrec <バインディング> <ボディ>)

構文: <バインディング>は次の形式でなければならない。

    ((<変数1> <初期値1>) ...)

<ボディ>には一つ以上の式が連続しなければならない。バインドしている変数リスト内に<変数>が二度以上現れた場合はエラーである。

意味: <変数>は、未定義の値を保持する新しい記憶域にバインドされる。その結果の環境で(不定の順序で)<初期値>が評価される。<変数>は一つ一つ対応する<初期値>の結果に割り当てられ、その結果の環境で<ボディ>が評価される。<ボディ>の最後の式の結果が返される。<変数>のバインディング一つ一つの領域はletrec式全体であり、したがって再帰手続きを相互に定義することが可能である。

  (letrec ((even?
            (lambda (n)
              (if (zero? n)
                  #t
                  (odd? (- n 1)))))
           (odd?
            (lambda (n)
              (if (zero? n)
                  #f
                  (even? (- n 1))))))
    (even? 88))
                  =>  #t

letrecに関する次の制限は非常に重要である。一つ一つの<初期値>は、いかなる<変数>への割り当ても参照も行なわずに評価できなければならない。この制限を侵害した場合はエラーである。この制限が必要な理由は、Schemeが引数を名前でなく値で渡すためである。letrecの一般的な使用法では<初期値>がすべてラムダ式である場合がもっとも多く、その場合この制限は自動的に満足される。

5.2.3 順次処理式

[[ライブラリ構文]] (begin <式1> <式2> ...)

<式>は左から右へ順次的に評価され、最後の<式>の値が返される。この型の式は、入出力などの副作用(18)を順次的に処理するために使用される。

  (define x 0)

  (begin (set! x 5)
         (+ x 1))           => 6

  (begin (display "4 plus 1 equals ")
         (display (+ 4 1))) => unspecified
  [さらに次のように印字される]  4 plus 1 equals 5  (19)

5.2.4 繰り返し

[[ライブラリ構文]](do ((<変数1> <初期値1> <ステップ1>)
                        ...)
                      (<テスト> <式> ...)
                      <コマンド> ...)

doは、繰り返しを構成する手続きである。バインドすべき変数の組、開始時に変数を初期化する方法、および繰り返しごとに変数を更新する方法を指定する。終了条件が満足されると<式> ...が評価されてループが終了する。

do式は次のように評価される。<初期値>の式が(不定の順序で)評価される。<変数>が新しい記憶域にバインドされ、初期値の式の評価結果が<変数>のバインディングに保管される。その上で繰り返し手順が開始する。

一回の繰り返しは<テスト>の評価で始まる。結果が偽であれば(section 7.3.1 論理式参照)<コマンド>の式が順次評価されて有効になる。<ステップ>の式が不定の順序で評価され、<変数>が新しい記憶域にバインドされる。<ステップ>の評価結果がその<変数>のバインディングに保管される。ついで次の繰り返しが開始する。

<テスト>が真値に評価されると<式>が左から右へ評価され、最後の<式>の値が返される。<式>が存在しない場合、do式の評価値は不定である。

変数のバインディング領域は、<初期値>を除くdo式全体で構成される。do式の変数リストに<変数>が二度以上現れた場合はエラーである。

<ステップ>は省略でき、その場合は(<変数> <初期値>)の代わりに、(<変数> <初期値> <変数>)と書いたものと同じ効果である。

  (do ((vec (make-vector 5))
       (i 0 (+ i 1))) 
      ((= i 5) vec) 
    (vector-set! vec i i)) => #(0 1 2 3 4)

  (let ((x '(1 3 5 7 9)))
    (do ((x x (cdr x))
         (sum 0 (+ sum (car x)))) 
      ((null? x) sum)))    => 25
[[ライブラリ構文]] (let <変数> <バインディング> <ボディ>)

Schemeの処理系によっては、"名前付let(named let)"と呼ばれるもう一つのlet構文が使用できる。これはdoよりもさらに一般的なループ構造が可能であり、再帰の表現にも使用できる。

名前付letの構文と意味は通常のletと同じであるが、<変数>が<ボディ>内部で手続きにバインドされる点が異なっている。この時、手続きの仮引数はバインドされた変数群であり、手続きの本体が<ボディ>である。したがって<変数>で命名された手続きを呼び出せば、<ボディ>の実行を繰り返すことができる。

  (let loop ((numbers '(3 -2 1 6 -5))
             (nonneg '())
             (neg '()))
    (cond ((null? numbers) (list nonneg neg))
          ((>= (car numbers) 0)
           (loop (cdr numbers)
                 (cons (car numbers) nonneg)
                 neg))
          ((< (car numbers) 0)
           (loop (cdr numbers)
                 nonneg
                 (cons (car numbers) neg)))))
    =>  ((6 1 3) (-5 -2))

5.2.5 遅延評価

[[ライブラリ構文]] (delay <式>)

delay構成手続きはforce手続きと組み合わせて使用して、その場で行なわない評価もしくは必要が生じた時の呼び出しを実装するものである。(delay <式>)は、プロミスと呼ばれるオブジェクトを返す。プロミスは将来のある時点で(force手続きによって)呼び出すことができ、その時点で<式>が評価されて結果が返される。複数の値を返す<式>の結果は不定である。

delayのさらに詳しい説明についてはforce(section 7.4 制御機能)参照。

5.2.6 バッククォート式

[[構文]] (quasiquote <qqテンプレート>)
[[構文]] `<qqテンプレート>

"バッククォート"式あるいは"疑似クォート"式は、あらかじめほとんどわかっているけれども一部不明なリスト構造やベクタ構造が欲しい時に、その構造を作成するのに有用である。<qqテンプレート>内にコンマがない場合、`<qqテンプレート>の評価結果は'<テンプレート>に等しい。ただし<qqテンプレート>内にコンマがあると、コンマに続く式は("クォートを取り払って(アンクォート)")評価され、コンマとそれに続く式の代わりにその評価結果が構造内に挿入される。コンマの直後にアットマーク(@)が書かれている場合は、それに続く式はリストに評価されなければならない。この時リストの開き括弧と閉じ括弧は"取り除かれ"、コンマアットマーク式に代わってそのリストの要素がそのあとに挿入される。コンマアットマークは、リストかベクタの<qqテンプレート>内部に書かなければならない。

  `(list ,(+ 1 2) 4)  =>  (list 3 4)
  (let ((name 'a)) `(list ,name ',name))
                      =>  (list a (quote a))
  `(a ,(+ 1 2) ,@(map abs '(4 -5 6)) b)
                      =>  (a 3 4 5 6 b)
  `((foo ,(- 10 3)) ,@(cdr '(c)) . ,(car '(cons)))
                      =>  ((foo 7) . cons)
  `#(10 5 ,(sqrt 4) ,@(map sqrt '(16 9)) 8)
                      =>  #(10 5 2 4 3 8)

バッククォート形式はネストできる。もっとも外側のバッククォートと同一のネストレベルで書かれたアンクォート要素に対してだけ置換が行なわれる。ネストレベルは、バッククォートが現れるたびにバッククォートの内側で1つ増加し、アンクォートが現れるたびにその内側で1つ減少する。

  `(a `(b ,(+ 1 2) ,(foo ,(+ 1 3) d) e) f)
            =>  (a `(b ,(+ 1 2) ,(foo 4 d) e) f)
  (let ((name1 'x)
        (name2 'y))
    `(a `(b ,,name1 ,',name2 d) e))
            =>  (a `(b ,x ,'y d) e)

2つの記法`<qqテンプレート>と(quasiquote <qqテンプレート>)はすべての点で等しい。,<式>(unquote <式>)に等しく、,@<式>(unquote-splicing <式>)に等しい。リストのcarが以上のシンボルの一つである2要素リストの場合、それに対してwriteが生成する外部表現による構文は、処理系によって異なる場合がある。

  (quasiquote (list (unquote (+ 1 2)) 4))
             =>  (list 3 4)
  '(quasiquote (list (unquote (+ 1 2)) 4))
             =>  `(list ,(+ 1 2) 4)
       i.e., (quasiquote (list (unquote (+ 1 2)) 4))

ただしこれは等しい構文(quasiquote (list (unquote (+ 1 2)) 4))と表現される場合がある。

シンボルquasiquoteunquoteunquote-splicingのいずれも、<qqテンプレート>内の上に述べた以外の場所に書かれた場合は生ずる動作は予測できない。

5.3 マクロ

Schemeプログラムでは、マクロと呼ばれる新しい導出式型を定義して使用することができる。プログラムで定義される導出式型の構文は次の通りである。

(<キーワード> <データ> ...)

ただし<キーワード>は、式の型を一意に定める識別子である。この識別子は構文キーワード、もしくは単にキーワードと呼ばれる。<データ>の数とその構文は、式の型に応じて異なる。

一つのマクロの具体化をそのマクロの使用と呼ぶ。マクロの使用をよりプリミティブな式に翻訳する方法を指定する規則の集合は、変換手続きと呼ばれる。

マクロ定義は次の2つの部分からなる。

マクロの構文キーワードは変数バインディングを隠す(20)場合があり、局所変数はキーワードバインディングを隠す場合がある。このパターン言語を使用して定義されたすべてのマクロは、次のように"名前の衝突"(21)がなく(ハイジーニック)、"参照上透過的"であり、したがってSchemeの辞書的スコープが保持される[KOHLBECKER86]、[HYGIENIC]、[BAWDEN88]、[MACROSTHATWORK]、[SYNTACTICABSTRACTION]。

5.3.1 構文キーワードのバインディング構成手続き

let-syntaxletrec-syntaxはそれぞれletletrecに似ているが、値が入る記憶域に変数をバインドするのではなく、構文キーワードをマクロ変換手続きにバインドする。構文キーワードをトップレベルで作成することもできる。section 6.3 構文定義参照。

[[構文]] (let-syntax <バインディング> <ボディ>)

構文: <バインディング>は次の形式でなければならない。

    ((<キーワード> <変換手続き仕様>) ...) 

<キーワード>の一つ一つは識別子である。<変換手続き仕様>の一つ一つはsyntax-rulesの発動である。<ボディ>には一つ以上の式が連続しなければならない。バインディングを実行中のキーワードのリストの中に、<キーワード>が二度以上現れた場合はエラーである。

意味: 指定変換手続きにバインドした<キーワード>をキーワードととするマクロを使用して、let-syntax式の構文環境を拡張した構文環境が得られる。その構文環境で<ボディ>が展開される。一つ一つの<キーワード>のバインディング領域は<ボディ>である。

  (let-syntax ((when (syntax-rules ()
                       ((when test stmt1 stmt2 ...)
                        (if test
                            (begin stmt1
                                   stmt2 ...))))))
    (let ((if #t))
      (when if (set! if 'now))
      if))                           =>  now

  (let ((x 'outer))
    (let-syntax ((m (syntax-rules () ((m) x))))
      (let ((x 'inner))
        (m))))                       =>  outer
[[構文]] (letrec-syntax <バインディング><ボディ>)

構文: let-syntaxに同じ。

意味: 指定変換手続きにバインドされた<キーワード>をキーワードとするマクロを使用して、letrec-syntax式の構文環境を拡張した構文環境が得られる。その構文環境で<ボディ>が展開される。<キーワード>のバインディング1つ1つの領域内に<バインディング>と<ボディ>が配置され、変換手続きはそれによってletrec-syntax式が導入するマクロの使用に式を翻訳できる。

  (letrec-syntax
    ((my-or (syntax-rules ()
              ((my-or) #f)
              ((my-or e) e)
              ((my-or e1 e2 ...)
               (let ((temp e1))
                 (if temp
                     temp
                     (my-or e2 ...)))))))
    (let ((x #f)
          (y 7)
          (temp 8)
          (let odd?)
          (if even?))
      (my-or x
             (let temp)
             (if y)
             y)))        =>  7

5.3.2 パターン言語

<変換手続き仕様>は次の形式である。

  (syntax-rules <リテラル> <構文規則> ...)

構文: <リテラル>は識別子のリストで、<構文規則>の一つ一つは次の形式でなければならない。

    (<パターン> <テンプレート>)

<構文規則>内の<パターン>は、マクロキーワードで始まるリスト<パターン>である。

<パターン>は識別子か定数であるか、でなければ下記のいずれかである。

    (<パターン> ...)
    (<パターン> <パターン> ... . <パターン>)
    (<パターン> ... <パターン> <略記号>)
    #(<パターン> ...)
    #(<パターン> ... <パターン> <略記号>)

<テンプレート>は識別子か定数であるか、でなければ下記のいずれかである。

    (<要素> ...)
    (<要素> <要素> ... . <テンプレート>)
    #(<要素> ...)

ただし<要素>は<テンプレート>であり、オプションで<略記号>を続ける場合がある。<略記号>は識別子"..."である(略記号識別子は、テンプレート内でもパターン内でも識別子として使用することはできない)。

意味: syntax-rulesが現れるごとに一連の名前の衝突を起こさない---ハイジーニックな---書き換え規則が指定され、新しいマクロ変換手続きがひとつ生成される。syntax-rulesが指定するマクロ変換手続きにキーワードが連結されたマクロの使用が、<構文規則>に含まれるパターンに一致するかどうかが<構文規則>の左端から比較される。一致するものが発見されると、テンプレートにしたがってマクロの使用がハイジーニックな方法で翻訳される。

<構文規則>のパターン内に現れる識別子は、パターンを開始するキーワードであるか、<リテラル>内にリストされるか、でなければ識別子"..."でない限り、パターン変数である。パターン変数は任意の入力要素に一致するもので、テンプレート内の入力要素の参照に使用される。<パターン>内に同一のパターン変数が二度以上現れた場合はエラーである。

<構文規則>のパターンの開始位置のキーワードは比較の対象には含まれず、パターン変数ともリテラル識別子とも見なされない。

理論的根拠:

キーワードのスコープは、キーワードとそれに結合するマクロ 変換手続きとをバインドする式もしくは構文定義によって定まる。 キーワードがパターン変数かリテラル識別子ならば、キーワードが let-syntaxによってバインドされたか、letrec-syntax によってバインドされたかにかかわらず、パターンに続くテンプレー トはキーワードのスコープ内にあることになる。

<リテラル>内に現れる識別子はリテラル識別子と解釈され、入力の対応する部分形式との一致が比較される。入力内の部分形式は、それが識別子であり、かつマクロ式内に出現する識別子とマクロ定義内に出現する識別子の両方が同一の辞書的バインディングを持っているか、二つの識別子が等しく両方とも辞書的バインディングを持っていないかのいずれかの場合に限ってリテラル識別子に一致する。

...が後続する部分パターンは、ゼロ個以上の入力要素に一致することができる。...が<リテラル>内に現れた場合はエラーである。パターンの内部では、識別子...は連続する空でない部分パターンの最後の要素の後ろに続けなければならない。

より形式的にいえば、次のいずれかの場合に限って入力形式FはパターンPに一致する。

マクロキーワードのバインディングスコープの内部で、いずれのパターンとも一致しない式にマクロキーワードを使用した場合はエラーである。

一致する<構文規則>のテンプレートにしたがってマクロの使用が翻訳されると、テンプレート内に現れるパターン変数は入力内の一致する部分形式で置き換えられる。一つ以上の識別子...が後続する部分パターン内に現れるパターン変数は、同一の数の識別子...が後続する部分テンプレート内でのみ使用することができる。パターン変数は指定通りに配分されて、入力内のすべての一致する部分形式で置き換えられて出力される。出力を指定通りに構築できない場合はエラーである。

テンプレート内に書かれていてもパターン変数でも識別子...でもない識別子は、リテラル識別子として出力に挿入される。リテラル識別子が自由な識別子として挿入された場合は、syntax-rulesのインスタンスが書かれているスコープ内の識別子のバインディングが参照される。リテラル識別子がバインド済み識別子として挿入された場合、リテラル識別子が自由な識別子に不用意に連結されるのを防止するために、リテラル識別子は実質的に名前が変更される。

例えばletcondがsection 8.3 導出式型の通りに定義されていたとすれば、この2つの識別子は(仕様通り)ハイジーニックであり、次はエラーではない。

  (let ((=> #f))
    (cond (#t => 'ok)))           => ok

condのマクロ変換手続きは=>を局所変数として、したがって式の1つとして認識し、マクロ変換手続きが構文キーワードとして処理するトップレベル識別子としては認識しない。したがって上の例は次のように展開される。

  (let ((=> #f))
    (if #t (begin => 'ok)))

次のように展開されるのではない。

  (let ((=> #f))
    (let ((temp #t))
      (if temp ('ok temp))))

これは無効な手続き呼び出しになる。


[Contents]   [Back]   [Prev]   [Up]   [Next]   [Forward]