Scheme の構文の復習
Scheme はかなりコンパクトな言語なので構文(特殊な作用のあるシンボル)自体もかなり少いがそれでも2桁の構文が存在し、処理系はその全てをサポートしないとまともにプログラムを実行できない。
分類
ここでは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
引数を評価してはいけない構文として quote
と quasiquote
の2つがある。これらはマクロ処理系内部で特別扱いされる必要がある。
quasiquote
は特殊な quote
で、内部に unquote
か unquote-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
unless
は if
の糖衣構文とも言える。同様にして and
や or
も if
で表現できる。case
は eqv?
手続きが必要なため 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する。構文の拡張タイミングは構文ごとに異なるのでその辺を柔軟に実装できるようにシステムを設計しなければならない。
名前の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
で定義される変数は一切使えない。 let
は lambda
の糖衣構文として成立ような仕様になっている。
letrec
系
letrec
は let
とは違い、変数の定義部でその 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
に展開される可能性があるため、マクロは事前に展開しなければならない。