Chapter 17. ジェネリックスから遅延束縛へ

Table of Contents
テンプレート実装のジェネリックス
例: 数に対するジェネリックス演算
ファンクタの特殊形としてのテンプレート
例: ループ生成のためのテンプレート
テンプレートを用いた遅延束縛のサポート

ATS における関数テンプレートのサポートは ATS のデザインと実装に深く根差しています。 第一に、関数テンプレートは ATS におけるコード再利用の一般的なアプローチを提供しています。 それは (妥当性の面で) とても柔軟で、必要になる実行時オーバーヘッドは最小のものです。 ATSPRE (つまり ATSLIB/prelude) と ATSLIB/libats の両方はテンプレートに基いていて、atsopt によって吐き出されるC言語コードが ATS ソースコード中のテンプレート実体を実装するためにそれらのライブラリにおけるテンプレートは使われます。 リンクする ATS のライブラリファイル (libatslib.alibatslib.so) は最小のものです。 それらは ATS ソースコードを実行バイナリにコンパイルするのに不要なことさえあります。

この章で紹介するコードとテストコードは オンライン から入手できます。

テンプレート実装のジェネリックス

この本の I 章で概説した通り、ネイティブのアンボックス化されたデータの存在下におけるパラメータ多相に関する問題に対して、関数テンプレートは自然な解決策です。 けれども、関数テンプレートはパラメータ多相の単なるサポートより多くのことが可能です。 myprint が次のインターフェイスの関数テンプレートだとしましょう:

fun{a:t@ype} myprint (x: a): void

値が与えられたとき、myprint はこの値に対するなんらかの表現を印字するものとしましょう。 例えば、myprint を次のように実装できます:

implement{a} myprint (x) = print_string "?"

myprint のこの実装はしばしば (完全な) 総称テンプレート実装と呼ばれます。 そのテンプレートパラメータに何の制限も課されていないからです。 次のように同じ実装を書く別の方法もあります:

implement(a) myprint<a> (x) = print_string "?"

明らかに、上記 myprint のジェネリックス実装は、与えられた値について特別な情報を何も印字できないという点において十分ではありません。 型 int の値のみをサポートする myprint を実装したくなるかもしれません:

implement myprint<int> (x) = print_int (x)

このとき print_int は与えられた整数を印字します。 myprint のこの実装は特殊テンプレート実装としばしば呼ばれます。 テンプレートパラメータに特別な型 (つまりこの例では int) を割り当てているからです。 次のコードは、リスト値 (つまりなんらかの型 T に対する型 List(T) の値) のための myprint を実装しています:

implement(a) myprint<List(a)> (xs) = case+ xs of | list_nil () => () | list_cons (x, xs) => (myprint<a> (x); myprint<List(a)> (xs))

myprint のこの実装は、しばしば部分的な総称テンプレート実装を呼ばれます。 この実装を使う myprint のインスタンスのために、そのインスタンスに対するテンプレートパラメータはなんらかの型 T において List(T) でなければなりません。 例えば、次のコードは2つの整数のリストのリストを印字するのに myprint のインスタンスを呼び出しています:

(* ** The output is "0123401234" *) val ys = $list{int}(0,1,2,3,4) val yss = $list{List(int)}(ys, ys) val ((*void*)) = myprint<List(List(int))> (yss) val ((*void*)) = print_newline((*void*))

関数テンプレートの実装はある特定の順序に従って順序付けられます。 これはジェネリックス順序 (genericity ordering) と呼ばれます: もし、ある実装が別の実装のインスタンスであるなら、前者のジェネリックスは後者のジェネリックス以下になります。 与えられたテンプレートインスタンスをコンパイルするのに必要なテンプレート実装の配置には (best-fit ではなく) first-fit 戦略が取られることに注意してください。 より具体的には、特殊テンレプートインスタンスに対するテンプレート実装の配置では、使用可能な最初の1つを探すのにレキシカルスコープの原理に従っています。

実際には、テンプレートインスタンスに対するテンプレート実装の配置にはほんの少し巧妙な点があります。 myprint2 を次のようなインターフェイスを持つ関数テンプレートとしましょう:

fun{a:t@ype} myprint2 (x: a): int

次のコードは myprint2 の部分的な総称テンプレート実装です:

// implement(a) myprint2<List(a)> (xs) = case+ xs of | list_nil () => () | list_cons (x, xs) => (myprint<a> (x); 1 + myprint2 (xs)) //

このテンプレート実装は、実際には予想される共同と違った振舞いをします。 実装本体で呼び出された myprint2 のインスタンスのテンプレートパラメータは、なんらかの (種 int の) 静的な項 N に対する list(a, N) 型になることに注意してください。 これはどのような型 T に対する List(T) にもマッチしないので、テンプレート myprint2 の呼び出されたインスタンスは与えられた myprint2 のテンプレート実装に従ってコンパイルできません。 この問題は、呼び出される myprint2 のインスタンスに、明示的に型 List(a) をテンプレートパラメータとして渡せば簡単に修正できます:

// implement(a) myprint2<List(a)> (xs) = case+ xs of | list_nil () => () | list_cons (x, xs) => (myprint<a> (x); 1 + myprint2<List(a)> (xs)) //

この例におけるインスタンス myprint2<List(a)> はしばしば再帰的なインスタンスと呼ばれます。 一般に、再帰的なインスタンスの使用を 避ける ことは良いプログラミング習慣です。 例えば、次のような myprint2 に相当する実装は再帰的なインスタンスを使っていません:

// implement(a) myprint2<List(a)> (xs) = let // fun aux (xs: List(a)): int = // case+ xs of | list_nil () => 0 | list_cons (x, xs) => (myprint<a>(x); 1 + aux(xs)) // in aux (xs) end // end of [myprint2<List(a)>] //

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