Schemeは、入力ポートから読んだり、出力ポートへ書きだしたりする入出力 (I/O)手続きをもっています。ポートはコンソールやファイルあるいは文字列 と結びつけることができます。
Scheme のリーダー手続きはオプションの引数としてポートをとります。 ポートが指定されない場合には現在の入力ポート(通常コンソール)を 仮定します。
読み込みは、文字ベースか行ベースかあるいはS式ベースでできます。
読み込みが実行されるたびに、ポートの状態は変化して次の読み込みが既に
読まれたものに続く対象を読み込みます。ポートに読み込むものが
なくなれば、リーダー手続きは end-of-file あるいは eof オブジェクト
と呼ばれる特別なデータを返します。このデータは、eof-object?
述語を
満たす唯一の値です。
手続きread-char
は次の文字をポートから読みます。
read-line
は次の行を読み、それを文字列として返します(最後の
改行は含まれません)。手続きread
は次のS式を読みます。
Schemeのライター手続きは書き出される対象とオプション引数として出力 ポートをとります。ポートが指定されない場合には現在の出力ポート( 通常はコンソール)が仮定されます。
書き出しは文字ベースあるいはS式ベースでできます。
手続きwrite-char
は与えられた(#\
以外の)文字を出力ポートに
書き出します。
手続きwrite
およびdisplay
はともに与えられたS式をポートに
書き出します。違いは、write
はマシンが読み取り可能なフォーマットにし、
display
はそうではない、ということです。たとえば、write
は
文字列に対しては、二重引用符を使い、文字に対しては #\
構文を
使いますが、display
はそうではありません。
手続きnewline
は出力ポート上で新しい行を開始します。
Scheme の I/O 手続きは対象が標準入力あるいは標準出力である場合には
引数でポートを指定する必要はありません。しかし、こうしたポートを
明示的に指定する必要がある場合には、引数なしの手続き
current-input-port
およびcurrent-output-port
がこれを提供してくれ
ます。すなわち、
(display 9) (display 9 (current-output-port))
は同じふるまいです。
ポートはファイルをオープンすることでそのファイルに結びつけることが
できます。手続き open-input-file
はファイル名を引数としてとり、
そのファイルに結びついた新しい入力ポートを返します。存在しない入力
ファイルをオープンしようとするか、既に存在する出力ファイルをオープン
しようとするとエラーになります。
ポート上でなにか I/O を実行したあとは、close-input-port
あるいは
close-output-port
でそれをクローズしなければなりません。
以下のように、一言hello
だけを含むファイルhello.txt
があるとします。
(define i (open-input-file "hello.txt")) (read-char i) => #\h (define j (read i)) j => ello
ファイル greeting.txt
は以下のようなプログラムをリスナーに食わせる
前には存在していないと仮定してください。
(define o (open-output-file "greeting.txt")) (display "hello" o) (write-char #\space o) (display 'world o) (newline o) (close-output-port o)
今や、ファイル greeting.txt
には以下の行が含まれているでしょう。
hello world
Scheme は call-with-input-file
と call-with-output-file
という
ポートのオープンと使用後のクローズの面倒をみてくれる手続きを
用意しています。
call-with-input-file
はファイル名と手続きをひとつ
引数としてとります。この手続きはファイル上にオープンした入力ポート
に適用されます。この手続きが終了するとポートのクローズを確認後
手続きの結果が返されます。
(call-with-input-file "hello.txt" (lambda (i) (let* ((a (read-char i)) (b (read-char i)) (c (read-char i))) (list a b c)))) => (#\h #\e #\l)
手続き call-with-output-file
は出力ファイルに対して類似のサービスを
提供します。
ポートが文字列に結びついていると便利なことが多いです。それで、
手続き open-input-string
は与えられた文字列にポートを結びつけます。
このポート上のリーダー手続きは文字列を読み取ります。
(define i (open-input-string "hello world")) (read-char i) => #\h (read i) => ello (read i) => world
手続き open-output-string
は最終的には文字列を生成するのに
使われる出力ポートを新たに生成します。
(define o (open-output-string)) (write 'hello o) (write-char #\, o) (display " " o) (display "world" o)
手続き get-output-string
を文字列ポート o
に蓄積された文字列を
取得するのに使えます。
(get-output-string o) => "hello, world"
文字列ポートは明示的にクローズする必要はありません。
Scheme のコードを含むファイルをロードする手続き load
については
すでに見ました。ファイルをロードすることは、そのファイルに
含まれているすべての Scheme のフォームを順に評価することにあります。
ファイルは別のファイルをロードすることができます。これは、
大きなプログラムをいくつものファイルに分割するときによく使います。
残念ながら、フルパスを指定されないかぎり、load
の引数のファイルは
Scheme のカレントディレクトリに依存します。フルパス名を与えることは
不便なこともあります。プログラムファイルをひとつかたまりとして
(おたがいの相対関係は維持したままで)移動したい場合で、おそらく
いろいろなマシンに移動したい場合があるからです。
MzScheme では、ロードするファイルを決定するのにとても
便利な load-relative
手続きを用意しています。
load-relative
は load
と同様にパス名を引数としてとります。
ファイル foo.scm
中に load-relative
の呼び出しがあると
引数のパスは呼んでいるfoo.scm
のディレクトリからのパスであると
推定してくれます。特に、このパス名は Scheme のカレントディレクトリ
とは独立しています。また、それゆえに、複数ファイルで開発するには
便利なものです。