Chapter 5. パラメータ多相

Table of Contents
関数テンプレート
多相関数
多相データ型
例: リストに対する関数テンプレート
例: リストのマージソート

コードの共有はプログラミングにおいて最も重要です。 型付きのプログラミング言語では、異なる型の値が同じ機能を必要としてしまうような状況にしばしば出会います。 例えば、文字, 整数, 文字列などをリストの要素とするリストの長さを算出する必要があるかもしれません。 明らかに、リストの長さを取る関数をそれぞれの要素の型毎に実装したくはありません。 おそらくコード重複の最悪の形でしょう。 どんなリストに対してもリストの長さを算出できるような唯一の関数を実装したいのです。 このリストの長さを取る関数は与えられたリストの要素の型をパラメータ化し、要素の型によらず一様な振る舞いをします。 このようなコードの共有はしばしば パラメータ多相 と呼ばれます。 これはオブジェクト指向プログラミングにおける継承ポリモーフィズムのようなその他のポリモーフィズムとは区別すべきです。

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

関数テンプレート

関数テンプレートは関数を実装するコードテンプレートです。 次のコードは、値をスワップする2つの関数を定義しています:

// typedef charint = (char, int) typedef intchar = (int, char) // fun swap_char_int (xy: charint): intchar = (xy.1, xy.0) fun swap_int_char (xy: intchar): charint = (xy.1, xy.0) //

型を無視すると swap_char_intswap_int_char の中身は同一です。 この種類のコードの重複を防ぐために、まずはじめに関数テンプレート swap を次のように実装し、それから swap_char_intswap_int_charswap を元に実装します:

// fun{ a,b:t@ype } swap (xy: (a, b)): (b, a) = (xy.1, xy.0) // fun swap_char_int (xy: charint): intchar = swap<char,int> (xy) fun swap_int_char (xy: intchar): charint = swap<int,char> (xy) //

関数テンプレートは ATS において第一級の値ではないことに注意すべきです: 関数テンプレートを表わすような式は存在しません。 キーワード fun に続く構文 {a,b:t@ype} はテンプレートパラメータや引数を表わしています。 一風変わった記号 t@ype はサイズが特定されていない型を表現する静的な項の種で、その型のサイズは型の値を表現するのに必要なバイト数です (型の値のサイズが全て同じであると仮定した場合ですが)。 ATS にはもう一つの種 type があり、サイズが正確に1ワードである型を表現する静的な項です。 1ワードというのは、32ビットマシンは4バイト、64ビットマシンでは8バイトです。 swap<char,int> という構文は (swap< の間にスペースを入れないでください)、 パラメータ ab をそれぞれ charint で置き換えた関数テンプレート swap のインスタンスを表わします。 構文 swap<int,char> も同様に解釈できるでしょう。

次のような swap の異なるスタイルの実装を見てみましょう:

fun{a:t@ype}{b:t@ype} swap2 (xy: (a, b)): (b, a) = (xy.1, xy.0)

上記の実装ではテンプレートパラメータは同時にではなく連続して与えられています。 次のコードは swap2 をインスタンス化する方法を示しています:

fun swap_char_int (xy: charint): intchar = swap2<char><int> (xy) fun swap_int_char (xy: intchar): charint = swap2<int><char> (xy)

>< は (GTLTという名前の) 特別な記号で、>< の間にスペースを入れてはいけません。

別の例として、(クロージャ) 関数を作る高階関数テンプレートを次に示します:

// typedef cfun (t1:t@ype, t2:t@ype) = t1 -<cloref1> t2 // fun{ a,b,c:t@ype } compose ( f: cfun (a, b), g: cfun (b, c) ) :<cloref1> cfun (a, c) = lam x => g(f(x)) // val inc_by_1 = lam (x:int): int =<cloref> x+1 val mul_by_2 = lam (x:int): int =<cloref> x*2 // val f_2x_1 = compose<int,int,int> (mul_by_2, inc_by_1) val f_2x_2 = compose<int,int,int> (inc_by_1, mul_by_2) //

f_2x_1 は、整数の引数に2を掛けた後に1を足す関数を表わしています。 同様に、値 f_2x_2 は、1を整数の引数に足した後に2を掛ける関数を表わしています。

ATS では、関数テンプレートは型検査されますが、C言語コードにはコンパイルされません。 その代わり、関数テンプレートは中間形式にコンパイルされます。 関数テンプレートのインスタンスのみがC言語コードにコンパイルされます。 1つの型パラメータを取る関数テンプレート foo と、 2つのインスタンス foo<T1> と foo<T2> が T1 と T2 型でプログラムで使用されていると考えてみましょう。 一般的に、プログラムがコンパイルされると、C言語の1つ1つの関数は foo のそれぞれのインスタンスから生成されます。 けれども、T1 と T2 が同じ名前を持っているなら、2つのインスタンスはC言語の1つの関数を共有するかもしれません。

混乱しないと思われる場合、これからこの本では関数という名前を関数テンプレートの意味で使うことがあることに注意してください。