シャドウイング
プログラミング言語 Scheme では予約語というものはない。 たとえば define
のような基本的な構文すら define
の機能に define
という名前が束縛されていると考えることが出来る。
再定義や代入には制限があるので予約語ではないからといってどこにでも現れることが出来るわけではないが、 let
などで隠蔽 (shadowing) する分にはどんな識別子でも対象に出来る。
こんなコードは全く正しい。
(let ((define *)
(a 2))
(define a 2))
もちろん、ほとんどの場合には単にわかり難くなるだけなので基本的な構文と同じ名前に別の意味を持たせるのは避けるべきだが、 Scheme における識別子の考え方を理解する上では重要な点でもある。
短縮表記
以下のような式を評価すると何が返ってくるか答えられるだろうか?
(let ('-) '1)
'
はあくまでも quote
を短縮した表記法だ。 そのことを思い出して書き換えると、上記は
(let ((quote -)) (quote 1))
と等しい。
quote
もまた単なる識別子であり隠蔽可能であるので、この quote
は -
と同じ手続きに束縛された変数に過ぎず、式を評価した結果は -1
となる。
import
Scheme では、 R6RS からモジュール化の仕組みが追加された。 __ライブラリ__と呼ばれる別のモジュールが公開する機能を import
すればその機能が使えるようになる。 ライブラリは規格 (処理系) が提供するものの他に、自分で作ることもできる。
さて、 import
の際には、名前を変更する機能がある。
たとえば define
を def
という名前で導入する場合には R6RS ではこのように書ける。
#!r6rs
(import (rename (rnrs) (define def))
(except (rnrs) define))
(def a 1)
(display a)
この場合は
-
(rnrs)
というライブラリからdefine
という名前をdef
にリネームして導入する -
(rnrs)
というライブラリからdefine
以外を導入する
ということを指定している。
違いの吸収
Scheme の仕様の改定で変更されたもののひとつとして define
が挙げられる。
R6RS の define
は束縛する値を省略することが出来る。 その場合は変数を束縛している値は未定義であるが、直後に set!
をするような場合には未定義であることを強調する書き方が出来た方が好ましい場合もある。 R7RS では define
で変数を定義するときには初期値を省略することは出来ない。
この違いを吸収して R7RS で R6RS 風の define
を使おうとすると以下のように書くことが出来る。
(import (rename (scheme base) (define r7rs:define))
(except (scheme base) define)
(scheme write))
;; R6RS 風の define を定義する
(define-syntax define
(syntax-rules ()
((_ i)
(r7rs:define i (if #f #t)))
((_ i f)
(r7rs:define i f))))
(define a) ; R7RS だが、 R6RS 風の define にできる
(set! a 'foo)
(display a)
まとめ
Scheme では仕様の改定によって新しい機能が追加されることも、既存の機能の挙動が変更されることもある。 また、処理系ごとの差異もかなり大きい。
しかし、基本的な言語機能を予約語として表現しないという性質によって名前の衝突は簡単に回避できるし、別バージョンの共存も簡単に成し得る。 いくつかの言語がバージョンアップと共に予約語の追加で破壊的な衝撃を齎すのと比較すると、穏当に移行可能なのが Scheme なのである。