効果 (effect) を持つプログラミングの機能を使うことで実行時に効果を生成することができます。 ところで効果とは一体何でしょうか? その答はいくぶん込み入っていて、評価モデルに依存しています。 この本では様々な種類の効果を徐々に説明します。 並行ではなく順番に評価がなされるようなプログラムを作る場合、評価に関してある式とその値が見分けがつかない時、その式は効果を持ちません。 例えば、値 3 と見分けがつかないので、式 1+2 は効果を持ちません。 効果を持たない式は純粋であるとも言えます。 他方、効果を持つ式はいかなる値とも区別ができます。 例えば、式 print("Hello") は効果を持ちます。 というのもその評価の結果、観測可能な振舞をひきおこすので、式はいかなる値とも見分けがつくからです。 この例では print("Hello") は I/O に対するある効果を持つと言えます。 もし式の評価が終了しない場合、その式もまた効果を持ちます。 例えば、次のような loop 関数を定義してみましょう:
すると次のような文脈で、式 loop() はいかなる値とも区別ができます: この文脈の空欄 [] を loop() で置き換えると、式の評価は完了しません。 空欄 [] をなんらかの値に置き換えると、その評価は文字列 "Terminated" を印字するでしょう。 式 loop はある非停止性の効果を持つを言えます。この章では、例外コントロールフローと永続化メモリ記憶、シンプルな I/O に関連するプログラミングの機能を説明します。 これらは実際のプログラミングで一般的に使われるものです。
この章に出てくるコードとテストのための追加コードは オンライン から得られます。
例外機構は、プログラムの評価中に発生した特殊な状態を通知するための効果的な方法です。 しばしばそのような特殊な状態はエラーを指しますが、エラーに関連しない事柄を指す例外を使うこともあります。
ATS では exn 型があらかじめ定義されています。 exn は、新しいコンストラクタによって宣言された拡張データ型であると思うかもしれません。 例えば次のように2つの例外コンストラクタを宣言します:
FatalError0 コンストラクタは引数を取らず、FatalError1 コンストラクタは引数を1つ取ります。 例外の値は exn 型の値で、例外コンストラクタを適切な引数に適用することで作られます。 例えば FatalError0() と FatalError1("division-by-zero") は2つとも例外の値です (もしくは単に例外と呼ぶこともあります)。 次のプログラムでは、整数の割り算を関数として実装しています:exception DivisionByZero of () fun divexn (x: int, y: int): int = if y != 0 then x / y else $raise DivisionByZero() // end of [divexn]
なんらかの式 exp が与えられとき ($raise exp) は raise 式です。 式 exp は値を返しますが、($raise exp) を評価すると当然、例外が発生してしまいます。 したがって、raise 式は評価されると値を返しません。つまり raise 式はいかなる型も取りうるのです。
発生した例外は捕捉することができます。 もし例外が捕捉されない場合、発生した例外はプログラムの評価を実行開始した地点で終了させます。 ATS では、try 式 (もしくは try-with 式) は (try exp with clseq) のように作られます。 この式では try キーワード、exp は任意の式、with もキーワード、そして clseq はマッチング節の列です。 try 式を評価すると、最初に exp が評価されます。 もし exp の評価が値を返したら、その値が try 式の値となります。 もし exp の評価が例外を発生させたら、clseq に列挙されたマッチング節のガードに対してその例外をマッチさせます。 もしマッチすれば、発生した例外は捕捉され、マッチしたガード最初の節の中身から評価が続行します。 もしマッチしなかったら、発生した例外は捕捉されません。 try 式ではしばしば with 部は例外ハンドラと呼ばれます。
例外の発生と捕捉をともなう例を見てみましょう。 次のプログラムでは、与えられたリスト中の整数の積を計算する3つの関数が定義されています:
fun listprod1 ( xs: list0 (int) ): int = ( case+ xs of | list0_nil () => 1 | list0_cons (x, xs) => x * listprod1 (xs) ) (* end of [listprod1] *) fun listprod2 ( xs: list0 (int) ) : int = ( case+ xs of | list0_nil () => 1 | list0_cons (x, xs) => if x = 0 then 0 else x * listprod2 (xs) // end of [list0_cons] ) (* end of [listprod2] *) fun listprod3 ( xs: list0 (int) ) : int = let exception ZERO of () fun aux (xs: list0 (int)): int = case+ xs of | list0_cons (x, xs) => if x = 0 then $raise ZERO() else x * aux (xs) | list0_nil () => 1 // end of [aux] in try aux (xs) with ~ZERO () => 0 end // end of [listprod3]
与えられたリスト中に整数 0 があった場合、その他の整数値によらずリスト中の整数の積は 0 である。
例外は簡単に使いこなせるようなプログラミングの機能ではありません。 また実際、例外の誤用はたくさんあります。 そのため例外を根気強く学び、注意深く使用してください。