有理数を整数の組で表現してみましょう。 もし有理数の値を表現するためにボックス化された抽象型 rat を宣言したら、 rat 型のそれぞれの値はヒープメモリに保存されます。 そのメモリはガベージコレクション (GC) によってのみ回収できます。 代わりに、 rat をアンボックス化された抽象型として宣言する別のアプローチを取ります。 そこで、次のような宣言になります:
この宣言の問題はあまりにも抽象的すぎることです。 rat 型のサイズについて何の情報もないので、 ATSコンパイラは rat 型の値を保存するのに必要なメモリサイズを知ることさえできません。 けれども、プログラマにとってこのような宣言が役に立たないというわけではありません。 実際にはこのような宣言が非常に重要になってくる場面があります。 このトピックは別の章で解説しようと思います。 ここでは、アンボックス化された抽象型を次のように宣言してみましょう: この宣言は、rat 型の値の表現は (int, int) 型の値の表現と同じであることを、単純に ATS コンパイラに伝えています。 けれども、この情報は ATS の型検査器にとって有効ではありません。 とりわけ、プログラム中で rat 型の値が整数のペアとして扱われると、型エラーを引き起こします。オンライン から取得できる次のコードは ratmod.sats という名前のファイルに保存されています。
exception Denominator exception DivisionByZero fun rat_make_int_int (p: int, q: int): rat fun ratneg: (rat) -> rat // negation fun ratadd: (rat, rat) -> rat // addition fun ratsub: (rat, rat) -> rat // subtraction fun ratmul: (rat, rat) -> rat // multiplication fun ratdiv: (rat, rat) -> rat // division
implement rat_make_int_int (p, q) = let fun make ( p: int, q: int ) : rat = let val r = gcd (p, q) in (p / r, q / r) end // end of [make] // val () = if q = 0 then $raise Denominator // in if q > 0 then make (p, q) else make (~p, ~q) end // end of [rat_make_int_int]
implement ratneg (x) = (~x.0, x.1) implement ratadd (x, y) = rat_make_int_int (x.0 * y.1 + x.1 * y.0, x.1 * y.1) // end of [ratadd] implement ratsub (x, y) = rat_make_int_int (x.0 * y.1 - x.1 * y.0, x.1 * y.1) // end of [ratsub] implement ratmul (x, y) = rat_make_int_int (x.0 * y.0, x.1 * y.1) implement ratdiv (x, y) = ( if y.0 > 0 then rat_make_int_int (x.0 * y.1, x.1 * y.0) else $raise DivisionByZero() // end of [if] ) (* end of [ratdiv] *)