OCamlからシステムにアクセスするときに使われる関数は Sys
と Unix
の二つのモジュールにまとめられています。一つ目の Sys モジュールは OCamlが実行される Unix およびその他のオペレーティングシステムで一般的な関数を含みます。二つ目の Unix モジュールは Unix に特有なものを全て含みます。
これ以降 Sys
と Unix
モジュールにある識別子はどちらのモジュールのものかを示すことなく使うことにします。つまり open Sys
および open Unix
を実行した状態であるということです。完全な例を示すときには、open Sys
と open Unix
を明示的に書くことにします。
Sys
および Unix
モジュールは Pervasives
モジュールに定義されている変数を上書きし、元の定義を隠してしまうことがあるので注意してください。例えば、 Pervasives.stdin
と Unix.stdin
は別物です。隠された定義にはプリフィックスをつけることでアクセスできます。
Unix ライブラリを使う OCamlのプログラムをコンパイルするには、次のようにします:
ここで prog
というプログラムは mod1
, mod2
そして mod3
という三つのモジュールから成ります。モジュールは別々にコンパイルすることもできます:
この場合、次のようにしてリンクします:
両方の例において、引数 unix.cma
は OCamlで書かれた Unix
ライブラリを表します。バイトコードコンパイラではなくネイティブコードコンパイラを使うには、 ocamlc
を ocamlopt
に、unix.cma
を unix.cmxa
に置き換えてください。
コンパイルツール ocamlbuild
を使っている場合、次の内容を _tags
ファイルに追加してください:
“toplevel” を言われる対話環境から Unix システムにアクセスすることもできます。実行している環境が C ライブラリの動的リンクに対応している場合、OCamlトップレベルを起動して次のディレクティブを入力します:
動的リンクに対応していない場合、システム関数がプリロードされた対話環境を作る必要があります:
このトップレベルは次のコマンドで起動できます:
シェル (コマンドインタープリタ) からプログラムを実行する場合、シェルは 引数 と 環境 を実行するプログラムに渡します。引数とはコマンドライン上でプログラムの名前の後ろに続く語です。環境とは variable=value の形をした文字列の集まりであり、環境変数のバインディングを表します。このバインディングは csh では setenv var=val で、 sh では var=val; export varでセットされます。
プログラムに渡された引数は文字列の配列 Sys.argv
に格納されます:
プログラムの環境は Unix.environment
関数で取得できます:
Sys.getenv
関数を使えば環境をより簡単に検索できます。
Sys.getenv v
は v
という環境変数に結び付けられた値を返します。環境変数が見つからなかった場合は Not_found
例外を出します。
最初の例として、引数を出力する echo
プログラムを示します。これは同じ名前の Unix コマンドと同じ動作です。
プログラムは exit
を呼ぶことで任意の場所で終了させることができます。
引数は呼び出し元のプログラムに送られる返り値です。問題のない場合には 0 を、エラーが起こった場合には0でない値を返すという慣習があります。プログラムの実行結果が条件として使われた場合、sh
シェルは返り値 0 をブール値 “true” に、0 でない全ての返り値を “false” として解釈します。
プログラムが全ての式を実行し終わって終了する場合、そのプログラムは暗黙的に exit 0
を呼びます。プログラムが補足されない例外によって途中で実行を終了する場合、そのプログラムは暗黙的に exit 2
を呼びます。
exit
関数は呼ばれたときに書き込み用にオープンされている全てのチャンネルのバッファをフラッシュします。at_exit
関数を使うと、プログラムが終了するときにこれ以外の動作をさせることができます。
最後に登録された関数が最初に実行されます。at_exit
関数を使って登録された関数は登録を解除することができませんが、これが本質的な制限になることはありません。グローバル変数を使って実行を変えることができるからです。
他に明示されていない限り、Unix
モジュールの全ての関数はエラーが起きたときに Unix_error
例外を出します。
Unix_error
例外の第二引数はエラーが起こったシステムコールの名前です。第三引数はエラーが起こったオブジェクトの名前を(可能な場合には)表します。例えば、ファイルの名前を引数として取るシステムコールの場合には、このファイルの名前が Unix_error
の第三引数となります。最後に、第一引数はエラーの種類を表すエラーコードを表します。エラーコードは error
というヴァリアント型に属しています。
この型のコンストラクタには posix で定義されるエラーが同じ名前と意味ですべて含まれ、加えて unix98, bsd のエラーの一部が含まれます。その他の全てのエラーは EUNKOWNERR
というコンストラクタになります。
例外が発生したとき、 try
によって補足されないエラーはプログラムの一番上まで上がっていき、プログラムを実行の途中で終了させます。小さいアプリケーションでは予見できないエラーを致命的なものとみなすことは良い習慣です。しかしその場合、エラーを分かりやすく表示することが望ましいです。エラーを分かりやすく表示するために、 Unix
モジュールには handle_unix_error
関数があります:
handle_unix_error f x
はまず引数 x
を関数 f
に適用します。この適用が Unix_error
を出した場合、エラーを説明するメッセージが表示され、exit 2
によってプログラムは終了します。次のプログラムは典型的な使用例です:
ここで関数 prog : unit -> unit
がプログラム本体を実行します。参考のために、 handle_unix_error
の実装を以下に示します。
prerr_xxx
の形をした関数は基本的には print_xxx
関数と同じ動作をしますが、書き込み先は stdout
ではなく stderr
となります。
error -> string
型をもつ error_message は引数の番号が表すエラーを説明するメッセージを返します (第 16 行)。プログラムに渡される第 0 引数 Sys.argv.(0)
にはプログラムを起動するのに使われたコマンドが格納されます (第 6 行)。
handle_unix_error
関数がプログラムの実行を終了させるような致命的なエラーを処理します。OCamlを使うことの利点は全てのエラーを明示的に処理することが求められ、エラーが発生するとプログラムの実行が終了することになるトップレベルでさえこれが求められることです。実際システムコールによるどんなエラーも例外を発生させるので、プログラムの実行は中断され、例外は明示的に補足・処理されるまで上に登る事になります。これによってプログラムが不整合状態で実行が続くことを防ぐことができます。
Unix_error
型のエラーにはもちろんパターンマッチを使うことができます。次の関数はこれからよく目にすることになります:
このコードは関数を実行してもし中断された場合にはもう一度繰り返すという処理を行います(4.5 節を参照)。
これから例を通して見ていくことですが、システムプログラミングでは同じパターンの処理が繰り返し出てきます。アプリケーションのコードが本質的な部分だけを含むように、共通する処理をまとめたライブラリ関数を定義しておくことが望ましいです。
自分で書いて自分で実行するプログラムではどんなエラーが出て、そのうちどれが実行を終了させるような致命的なエラーかが分かるものですが、ライブラリ関数の場合には実行されるコンテキストが分からないのでどれが致命的なエラーなのかは通常分かりません。かといって全てのエラーが致命的であると仮定することもできません。そのためプログラムを止めるのか、それとも無視するのか呼び出し元に判断させるために、エラーを呼び出し元に伝えることが必要になります。
しかし、ライブラリ関数を普通に実装すると発生したエラーをそのまま呼び出し元に伝えることができません。システムを整合状態に置くことが求められるためです。例えばファイルを開いてそのファイルディスクリプタを使って操作を行うライブラリ関数は、ファイルへの操作でエラーが生じた場合を含めた全ての場合においてファイルディスクリプタを閉じる処理を行う必要があります。ファイルディスクリプタがリークしてファイルディスクリプタを使いきってしまうことを防ぐためです。
ファイルに対する操作を引数として受け取る場合もあります。この場合、いつどのように操作が失敗するかを知ることは (呼び出した側でなければ) できません。そのため操作の本体は “最終処理” コードで守ることが必要になります。このコードは関数が例外を出したかどうかにかかわらず、関数が帰る直前に実行されます。
try
…finalize
構文は OCamlにビルトインでは用意されていませんが、簡単に定義することができます 1。
この関数はメインの処理 f
と最終処理 finally
およびそれらの引数 x
と y
を受け取ります。最初にプログラムの本体 f x
が実行され、その結果は最終処理 finally y
が実行されてから返されます。プログラムの実行で例外 exn
が起こった場合、最終処理が実行されてからもう一度 exn
を出します。メインの処理と最終処理の両方が失敗した場合、最終処理の例外が出されます(メインの処理の例外が出されるようにすることもできます)。
これからこのコースでは例でよく使う try_finalize
などの関数をまとめた補助ライブラリ Misc
を使います。必要に応じてライブラリの関数を紹介するほか、インターフェースは付録にあります。このコースに出てくる例をコンパイルするには、 Misc
モジュールの定義をまとめておく必要があります。
Misc
モジュールにはこのコースでは直接使用しない可視化のための関数も含まれています。これらの関数は Unix
ライブラリを強化するためのもので、いくつかの関数の振る舞いを上書きします。そのため Misc
ライブラリを使う場合は Unix
の後に読み込まれる必要があります。
このコースにはたくさんの例が含まれています。これらはバージョン 4.05.0の OCamlでコンパイルされることを確認しています。古いバージョンではプログラムを若干改変する必要があります。
例には二つの種類があります: とても一般的で再利用が可能な “ライブラリ関数” と小さなアプリケーションです。これら二つを区別することは重要です。ライブラリ関数の場合には実行時のコンテキストをできるだけ一般的なものと仮定して、インターフェースを熟慮し、全ての特殊ケースを扱うようにします。一方小さなアプリケーションの場合には、多くのエラーは致命的なものであり、プログラムの実行を停止させます。そのためエラーが起きた時にはその原因を伝えるだけで十分であり、システムを整合状態へと戻す処理は必要ありません。