R6RS:概要と例
概要
R6RSは以下の4部のドキュメントから構成されています。
R6RS (本体) 90pp
コア言語仕様。Schemeの文法、意味、モジュール、マクロ、 基本データ型とそれに関する手続き(base library)が定められています。 これまでのR5RSに相当する部分だと思って良いでしょう。ただし、R5RSに入っていた ライブラリ関数の一部は次の「標準ライブラリ」の方に移されています(I/Oなど)。
R6RS 標準ライブラリ 71pp
ポータブルなライブラリを書いてゆくために必須と考えられたライブラリが定義されています。 I/O、バイナリデータの扱い、文字コード変換、例外システム、ユーザ定義レコード型、 低レベルマクロ(syntax-case)、ハッシュテーブル、R5RS互換ライブラリなど。 これらのライブラリのほとんどは複合ライブラリ (rnrs (6)) をインポートすることで 使うことができるようになります。
eval、ペアに対する破壊的変更 (set-car!, set-cdr!)、文字列に対する 破壊的変更 (string-set!, string-fill!)、R5RS互換関数のいくつかも こちらで定義されています。これらは明示的にライブラリ名を指定してインポート しないと使えません。
詳しくはR6RS:標準ライブラリにて。
R6RS 参考付録 6pp
正式な仕様に含めるまでには至らなくても、ガイドラインを示しておくことが 有用であるようないくつかの事項について触れています。
- 実装は、R6RSの仕様に完全準拠する以外に、処理系独自の拡張モードを用意しても良い。
R5RSまでは規定された文法や定義域を逸脱した場合の動作について実装の裁量に任される 範囲が広く、それを利用して処理系が拡張を実装することが良く行われていましたが、 R6RSではそのような逸脱時のエラーの処理が厳格化されており、 処理系の裁量範囲が狭くなりました。例えばcarは空リストを受け取ったら &assertion例外を投げなければいけません。R5RSまでは、(car '())は エラー(R5RS的に不正なプログラム)である、とだけ規定されていてその処理は 処理系依存だったので、処理系の裁量で (car '()) => () とすることも 可能でした。R6RSでは、「準拠モード」を名乗る時はちゃんと例外を投げてね、 でも処理系独自のモードを別に持っててもいいよ、ってことになっています。
なぜわざわざこういう断りが入っているかというと、R5RSまでは仕様はある意味 ミニマムを定めるもので、拡張はいくらでも好きなように、だったのが、R6RSでは マキシマムも定めている箇所があり、R6RSの範囲内では拡張が制限される、という 事情からです。- 大文字小文字は区別されるが、されないモードを持ってても良い
R5RSまでと違って(そしてLispの長年の伝統を破って)、 シンボル等はread時に大文字小文字を区別するようになりました。 ただ、処理系は以前のように大文字小文字を区別しないモードを持っても良く、 その場合は #!fold-case と #!no-fold-case というトークンで モードを切り替えるようにすることが推奨されています。
- []のガイドライン
R5RSまでは処理系拡張用に予約されていた[]ですが、R6RSでは()と全く同じ意味に なりました。(+ 1 2)と書いても[+ 1 2]と書いても同じです (ただし、それぞれの 括弧はマッチしてないとだめです)。 この意図はそもそも、手続き呼び出し以外の構文の括弧を見やすくするためです。 そこで、「こういう場合に[]を使うといいよ」というガイドラインが示されています。 例えばletの変数と初期化式を囲むのに使う、とか:
(let ([var1 init1] [var2 init2]) body ...)
- Schemeスクリプトの書法
Schemeスクリプトは現場では何かと便利なものですが、shebang行などOSに依存する 部分があるのと、「バッチコンパイラだけを提供する」実装を許容するために R6RS本体には含まれませんでした。それでも色々な実装で共通して使える スクリプトが書けると便利なので、ここでポータブルなスクリプトの書式を示しています。
- ソースコードの形式
ソースコードを現実のシステムにどう格納すべきか、についても、 色々な実装を許容するためにR6RS本体では触れられていません。 例えばポータブルなソースはutf-8でUnicode Normalization Form Cを使うこと、 とか、ソースファイル中にライブラリを複数含めても良い、とか。
- ライブラリのバージョニング
R6RSのlibraryフォームとimportフォームはそれぞれ、定義するライブラリと importするライブラリのバージョンを指定できます (importの場合はバージョンの 範囲も指定できる)。それらをどう解釈すべきかのガイドラインです。
- ライブラリ名
ライブラリの名前づけについてR6RS本体では制限を設けていませんが、 ポータブルで広く使われるライブラリを書くならこういうガイドラインに 従った方がいいでしょう、ということで、Javaのようにドメイン名を ユニークな識別子として使うことや、ファイルでソースを提供するなら ライブラリ名とパス名を合わせること、などが示されています。 (R6RSライブラリ名はリストなので、swiss.mit.eduで提供されるライブラリは (edu mit swiss foo) のように名付けられることになります)。
これらはあくまでガイドラインなので、これに従わなければR6RS準拠にならない ってわけではありません。
R6RS 理由説明 20pp
いくつか設計上の選択が考えられる場合に、なぜR6RSでは規定されているような 選択をしたか、という説明です。R5RSまでは本文中に理由が必要に応じて埋め込まれて いましたが、R6RSでは分量も増えたので別になりました。 R6RSについて理解を深めたり議論したい場合はまずこれを参照して下さい。
例
R6RS準拠のSchemeプログラムは、ひとつのトップレベルプログラムと、 いくつかのライブラリから構成されます。
トップレベルプログラムはSchemeプログラムの実行が開始されるコードです。 先頭で必要とするライブラリをimportして、後はR5RSまでと同じように 定義や式を並べておきます。定義と式はどのような順序で現れても構いません。 上から順番に実行されます。
(import (rnrs) ; R6RS baseと標準ライブラリをインポート (mylib mystuff)) ; (mylib mystuff)ライブラリをインポート (define (usage) (display "Usage: ...\n") (exit)) (define (do-it args) ....) (let ((args (command-line))) (when (null? (cdr args)) (usage)) (do-it (cdr args)))
srfi-22と違って、mainという手続きが特別扱いされるということはありません。 なので、最低ひとつは定義ではない式を書いておいて、そこから実行が開始されるように しておく必要があります。 (そのため、main手続きを使っているsrfi-22プログラムとR6RSトップレベルプログラム とは非互換になります)。 コマンドラインには標準ライブラリのcommand-line手続きで アクセスできます。
ライブラリは、(1)全体がlibraryフォームで囲まれること、(2)先頭にexportフォームが 置かれること、(3)定義でない式があれば、それは全ての定義の後に置かなければならないこと、 を除いてはトップレベルプログラムと変わりません。
(library (mylib mystuff) (export very-handy-procedure ...) (import (rnrs)) (define (very-handy-procedure arg) ...) ... )
R5RSと違って、同じ変数を同一ライブラリやトップレベルプログラム内で複数回定義したり、 importした変数をdefineで再定義したりすることは出来ません。 これによって、R5RSまでで生じていた再定義による曖昧性は排除されます。 R5RSではダークコーナーだったトップレベルのセマンティクスも明確化されています。
標準にある手続きと同名の手続きを(例えば独自の拡張をするために)定義したい場合は、 importの時点でその手続きを除くかrenameするかします。 例えば次のようにすることにより、空リストを渡されたら空リストを返すcarとcdrを 定義することができます。
(import (rename (rnrs) (car orig-car) (cdr orig-cdr))) ;リネームしてimport (define (car pair) (if (null? pair) '() (orig-car pair))) (define (cdr pair) (if (null? pair) '() (orig-cdr pair)))
ところで、再定義が出来ないと対話実行するREPLで困るじゃないか、と思うかもしれませんが、 REPLはR6RSの範囲外なので大丈夫です。R6RS処理系にREPLがついていたら、 それはあくまで処理系独自の拡張なので、どういうセマンティクスにしようが 処理系の勝手です。