I/O

Schemeは、入力ポートから読んだり、出力ポートへ書きだしたりする入出力 (I/O)手続きをもっています。ポートはコンソールやファイルあるいは文字列 と結びつけることができます。

7.1  読み込み

Scheme のリーダー手続きはオプションの引数としてポートをとります。 ポートが指定されない場合には現在の入力ポート(通常コンソール)を 仮定します。

読み込みは、文字ベースか行ベースかあるいはS式ベースでできます。 読み込みが実行されるたびに、ポートの状態は変化して次の読み込みが既に 読まれたものに続く対象を読み込みます。ポートに読み込むものが なくなれば、リーダー手続きは end-of-file あるいは eof オブジェクト と呼ばれる特別なデータを返します。このデータは、eof-object?述語を 満たす唯一の値です。

手続きread-charは次の文字をポートから読みます。 read-lineは次の行を読み、それを文字列として返します(最後の 改行は含まれません)。手続きreadは次のS式を読みます。

7.2  書き出し

Schemeのライター手続きは書き出される対象とオプション引数として出力 ポートをとります。ポートが指定されない場合には現在の出力ポート( 通常はコンソール)が仮定されます。

書き出しは文字ベースあるいはS式ベースでできます。

手続きwrite-charは与えられた(#\以外の)文字を出力ポートに 書き出します。

手続きwriteおよびdisplayはともに与えられたS式をポートに 書き出します。違いは、writeはマシンが読み取り可能なフォーマットにし、 displayはそうではない、ということです。たとえば、writeは 文字列に対しては、二重引用符を使い、文字に対しては #\ 構文を 使いますが、displayはそうではありません。

手続きnewlineは出力ポート上で新しい行を開始します。

7.3  ファイルポート

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 

7.3.1  ファイルポートの自動オープンと自動クローズ

Scheme は call-with-input-filecall-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 は出力ファイルに対して類似のサービスを 提供します。

7.4  文字列ポート

ポートが文字列に結びついていると便利なことが多いです。それで、 手続き 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"

文字列ポートは明示的にクローズする必要はありません。

7.5  ファイルのロード

Scheme のコードを含むファイルをロードする手続き load については すでに見ました。ファイルをロードすることは、そのファイルに 含まれているすべての Scheme のフォームを順に評価することにあります。

ファイルは別のファイルをロードすることができます。これは、 大きなプログラムをいくつものファイルに分割するときによく使います。 残念ながら、フルパスを指定されないかぎり、load の引数のファイルは Scheme のカレントディレクトリに依存します。フルパス名を与えることは 不便なこともあります。プログラムファイルをひとつかたまりとして (おたがいの相対関係は維持したままで)移動したい場合で、おそらく いろいろなマシンに移動したい場合があるからです。

MzScheme では、ロードするファイルを決定するのにとても 便利な load-relative 手続きを用意しています。 load-relativeload と同様にパス名を引数としてとります。 ファイル foo.scm 中に load-relative の呼び出しがあると 引数のパスは呼んでいるfoo.scmのディレクトリからのパスであると 推定してくれます。特に、このパス名は Scheme のカレントディレクトリ とは独立しています。また、それゆえに、複数ファイルで開発するには 便利なものです。