テンプレートを用いた遅延束縛のサポート

オブジェクト指向プログラミング (OOP) の意味について聞かれたとき、アラン・ケイは彼にとっての OOP はメッセージ、状態プロセスの局所的な保持, 保護, 隠蔽と全てに対する極端な遅延束縛 (late-binding) であると答えました。

ATS では、関数テンプレートは (関数呼び出しの) 遅延束縛に高度に柔軟なアプローチを提供します。 はじめに、なぜ遅延束縛が魅力的なのか知るために、単純な例を見てみましょう。 次のコードは、整数もしくは (倍精度の) 浮動小数点数を表わすような型である、データ型 intfloat を宣言しています:

// datatype intfloat = INT of int | FLOAT of double //

intfloat の値を印字するために、次のような print_intfloat を実装できます:

// fun print_intfloat (x: intfloat): void = ( case+ x of | INT(int) => print_int(int) | FLOAT(float) => print_double(float) ) //

このとき、print_intprint_double は、それぞれ整数と (倍精度の) 浮動小数点数を印字する単相関数です。 整数と浮動小数点数を印字するにはもちろん数多くの方法があります。 けれども、print_intfloat は唯一1つの印字方法しか使えません。 すなわち print_int を通じて整数を表わし、print_double を通じて浮動小数点数を表わすしかありません。 このような柔軟性の欠如を回避する方法の1つは、次のような高階関数 fprint_intfloat を定義することです:

// fun fprint_intfloat ( x: intfloat , print_int: int -> void , print_double: double -> void ) : void = ( case+ x of | INT(int) => print_int(int) | FLOAT(float) => print_double(float) ) //

fprint_intfloat を用いると、後から print_intprint_double の実装を選択できます。 この点で、高階関数は遅延束縛の一種をサポートしていると言えます。 けれども、そのような高階関数の使用に問題がないわけではありません。 基本的に、print_int を直接もしくは間接的に呼び出すどのような関数も、高階関数になる必要があります。 print_double についても同様です。 print_intprint_double のように扱う必要がある関数が増えた場合、高階関数を多様するこのプログラミングスタイルは極端に不恰好なものになりえます。

高階関数を使用する代わりに、テンプレート関数を使って (関数呼び出しの) 遅延束縛をサポートできます。 例えば、次のコードは型 intfloat の値を印字するテンプレート関数 tprint_intfloat を実装しています:

// extern fun{} tprint_int(int): void extern fun{} tprint_double(double): void extern fun{} tprint_intfloat(intfloat): void // (* ****** ****** *) // implement tprint_int<> (x) = print_int(x) implement tprint_double<> (x) = print_double(x) // (* ****** ****** *) // implement tprint_intfloat<> (x) = ( case+ x of | INT(int) => tprint_int<> (int) | FLOAT(float) => tprint_double<> (float) ) //

tprint_inttprint_double のデフォルト実装はそれぞれ print_intprint_double であることに注意してください。 予想されることですが、次のコードは2行印字します:

// val () = ( tprint_intfloat<> (INT(0)); print_newline() ) (* end of [val] *) // val () = ( tprint_intfloat<> (FLOAT(1.0)); print_newline() ) (* end of [val] *) //

このとき、出力される最初の行は文字列 "0" で、次の行は文字列 "1.000000" になります。 次のコードもまた2行印字します:

local // implement tprint_int<> (x) = print! ("INT(", x, ")") implement tprint_double<> (x) = print! ("FLOAT(", x, ")") // in (* in-of-local *) // val () = ( tprint_intfloat<> (INT(0)); print_newline() ) (* end of [val] *) // val () = ( tprint_intfloat<> (FLOAT(1.0)); print_newline() ) (* end of [val] *) // end // end of [local]

このとき、出力される最初の行は文字列 "INT(0)" で、次の行は文字列 "FLOAT(1.000000)" になります。 この後者のケースでは、テンプレートインスタンス tprint_int<>tprint_double<> の呼び出しは、キーワード localin の間で与えられている tprint_inttprint_double の実装に従ってコンパイルされます。

この章で紹介したコード全体とテストコードを含む intfloat.dats ファイルはオンラインから入手できます。