Open3

Scheme の構文の復習

okuokuokuoku

Scheme はかなりコンパクトな言語なので構文(特殊な作用のあるシンボル)自体もかなり少いがそれでも2桁の構文が存在し、処理系はその全てをサポートしないとまともにプログラムを実行できない。

okuokuokuoku

分類

ここではR7RS Schemeのサブセット(yuni Scheme)を想定している。yuni SchemeはR6RSとR7RSの共通部分および既存のいわゆる LL の機能で無理なく実現できるSchemeを企図したもの。例えば、Schemeの重要な機能である call/cc はyuni Schemeには存在しない。

補助構文

補助構文は、単体では使用されず、他の構文と組み合わされた場合にだけ意味があるものを指す。

... => else unquote unquote-splicing の5つが該当する。

Schemeが特殊なのは、これらの補助構文を普通の変数として使用した場合は、補助構文本来の機能を失わさせる必要がある点。例えば、 (define else #t) のように else を定義した場合は else 本来の機能を失ってしまう。

quote

引数を評価してはいけない構文として quotequasiquote の2つがある。これらはマクロ処理系内部で特別扱いされる必要がある。

quasiquote は特殊な quote で、内部に unquoteunquote-splicing がある場合は quote していない通常の式として扱わなければならない。

syntax-rules

syntax-rules は特殊な構文なので一旦無視する。↓のbind可能な構文の一種と言えるが、パターンマッチのような複雑な機能を内包しているため特別扱いする方が都合が良いことが多い気がする。

bindしない構文

"bindしない構文" を正確に表現する用語はSchemeの標準規格には存在しない。これらはいわゆる define-macro なマクロと同様のインターフェースで実現できる -- マクロ本体が環境を受けとる必要がない。

and begin case cond do if or parameterize set! unless when の11個が該当する。

when unless は愛用してるけど、 parameterize とか do は殆ど使わないな。。Schemeでは一般に if の else 節を省略できるため、 when unlessif の糖衣構文とも言える。同様にして andorif で表現できる。caseeqv? 手続きが必要なため if だけでは表現できない。

名前のbindが可能な構文

bindする構文は、環境を拡張する可能性があるため、いわゆる define-macro の単純な変形インターフェースでは実装できない。

define define-record-type define-syntax define-values guard lambda let let* let*-values let-syntax let-values letrec letrec* letrec-syntax case-lambda の15個が該当する。

名前に syntax が入っている構文は変数ではなく構文をbindする。構文の拡張タイミングは構文ごとに異なるのでその辺を柔軟に実装できるようにシステムを設計しなければならない。

okuokuokuoku

名前のbindタイミングのパターン

lambda

Schemeにおける名前の基本は lambda と言える。Rubyのブロックのように直接的に同じ構文を備える言語もある。

(lambda (a b c) <ここでは a b cが使える>)
<ここでは a b c は使えない> 

define 型を除いて、一般に名前定義の寿命はその構文の内部の式に限られる。

let

let 型のパターンは一般的なプログラミング言語の変数定義に見た目が近い。ただし Scheme では各変数の初期化の順序が規定されていない ため注意が必要。上から実行する処理系と下から実行する処理系の両方があるし、最適化によって順序が変わることさえある。

(let ((a <ここでは a b c は使えない>)
      (b <ここでは a b c は使えない>)
      (c <ここでは a b c は使えない>))
  <ここでは a b c が使える>)
<ここでは a b c は使えない> 

定義部では、その let で定義される変数は一切使えない。 letlambda の糖衣構文として成立ような仕様になっている。

letrec

letreclet とは違い、変数の定義部でその letrec で定義した名前を使用できる。

(letrec ((a <ここでは a b c が使える>)
         (b <ここでは a b c が使える>)
         (c <ここでは a b c が使える>))
  <ここでは a b c が使える>)
<ここでは a b c は使えない> 

letrec は再帰的な手続をbindするのによく使われる。

(letrec ((even? (lambda (i) (if (= 0 i) #t (odd?  (- i 1)))))
         (odd?  (lambda (i) (if (= 0 i) #f (even? (- i 1))))))
  (display (even? 12)) (newline) ;; => #t
  (display (even? 11)) (newline) ;; => #f
  (display (odd? 12)) (newline) ;; => #f
  (display (odd? 11)) (newline)) ;; => #t

これは let では表現できない。 even? の定義では odd? を参照している。

define

define は他所のプログラミング言語の変数定義にもっと近いもので、 構文外部にも有効な名前を定義できる

(define a <ここでは a b cが使える>)
(define b <ここでは a b cが使える>)
(define c <ここでは a b cが使える>)
<ここでは a b c が使える>

つまり、あるシンボル a がマクロなのか変数なのかを判別するためには、プログラムをスキャンして define 系の構文を探すことで事前に define 系構文を検出する必要がある。

更にややこしいことに、任意の記述が define に展開される可能性があるため、マクロは事前に展開しなければならない。