[ Eiffel Liberty | GUERL | sOOap ]
by
Alan A. Snyder
and
Brian N. Vetter
6 - 総称 6.1 制約されていない総称 6.1.1 多重のパラメータ化 6.2 制約された総称
総称<genericity>は、通常、コンテナのクラス群の文脈に適用されます。STACK、QUEUE、あるいは TREE といったデータ構造は、その主な機能が他のオブジェクトを格納することであるという、コンテナクラスとして分類することができます。従来のプログラミングでは、スタックの抽象データ型を書き、その抽象的な操作である push、pop などを幅広く多様なデータ構造へ適用できるということは、悪名タカク困難なことでした。C のような、弱い型付け規則を持つ言語は、型指定<type cast>といったものに準じる低水準の作業にそれら自体の限界を迂回することを許しています。取られた第二の接近方法は、あるデータ構造をコンテナへ出し入れするために、バイト(あるいはワード)を用いて、それ単位で、低水準で作業することでした。これらの調合の最後の結果は、働いていますが、極端に不安定であり、採用しがたいコンテナの構造です。
総称的なクラスは、型のパラメータ化を受け入れるクラスです。たとえば、STACK クラスは、INTEGER、VEHICLES、あるいは DMV_PEOPLE だけを受け入れるように宣言することもできます。もしこれがその事例であるならば、総称的に制約されている、と言われます。この話題は、次の節で扱われます。今は、私たちは、制約されていない総称、つまり、総称のより単純でより一般的な形態、に焦点を合わせましょう。
クラスを総称として宣言するために、私たちは、どんな型のオブジェクトも受け入れるように宣言することもできるスタックを宣言する、この単純な例を提供します。
class STACK [T] feature ... end -- STACKこの例で新たに導入した構文だけが、クラス STACK の宣言のあとにある、総称的なパラメータ [T] です。角括弧は、単に、STACK を総称として導入し、T が、STACK の各実例の形式的総称パラメータになります。
STACK をクラスによってパラメータ化されるように宣言したから、私たちは、このスタックをインスタンス化する際のオブジェクトの実体というよりも、実パラメータ、すなわちクラスの型を渡さなければなりません。つまり、私たちは、オブジェクトの実体の名前の代わりに、STACK を宣言する際のクラスの名前を渡さなければなりません。このパラメータ、つまり、下記の TOKEN と SEM は、実総称パラメータと呼びます。次をよく見てください。
class LUNCH_COUNTER feature tray_stack : STACK[ TRAY ]; napkin_dispenser : STACK[ NAPKIN ] ... end -- LUNCH_COUNTERいま、私たちは、二つの STACK を宣言しました。一つ目は、型 TRAY のオブジェクトを受け入れる tray_stack であり、二つ目は、型 NAPKIN のナプキンを受け入れる napkin_dispenser です。次の明らかな質問は、私たちは、push と pop のようなスタックの内部の操作を書くときに、この情報をどのように使用するか、ということです。次をよく見てください。
class STACK [T] feature push( element : T ) is do storage( pos ) := element; pos := pos + 1; end -- push end -- STACKヴィオラ<??viola>!。私たちは、私たちの目的のための STACK の本体の文脈においては、どこでも T を使用できます。
この実装は、私たちは storage と呼ぶ実体を持っていないという点で、現実的ではありませんが、もしそうしたやり方を選ぶのなら、それで実装することもできました。同様に、pop、top、そして STACK の残りの操作は、同じ方法で実装します。制約されていないクラスの実際の使用は、どんな風にみえるでしょう?
class LUNCH_COUNTER feature tray_stack : STACK[ TRAY ]; napkin_dispenser : STACK[ NAPKIN ] ... init is do ... napkin_dispenser.pop( end -- init end -- LUNCH_COUNTERいま、私たちは、どんな所与の型のオブジェクトでも受け入れるであろうスタックを生成する能力を持っています。この総称は、その限定的な性質によって、制約されていない、と呼びます。けれども、注意すべきことは、もし私たちが、次のような、新たなクラスの型を宣言したならば、
class FANCY_TRAY inherit TRAY end; end -- FANCY_TRAYTRAY として宣言したすべての実体は、tray_stack の活動における部分を取るのにあらゆる権利を持っている、ということです。つまり、次は、合法的です。
class LUNCH_COUNTER feature tray_stack : STACK[ TRAY ] ... init is do ... tray_stack.push( new_tray ) ... end -- init end -- LUNCH_COUNTERSTACK が制約されていないという理由で、私たちは任意にどんな型のオブジェクトでも tray_stack へプッシュできることを許されている、と考えないでください。事実は、私たちは、型 TRAY として宣言されたどんなオブジェクトでも、あるいは tray_stack 上のその子孫のどんな型でも、プッシュできる、ということです。覚えておいていただきたいのは、Eiffel の型システムは、完全にクラスに基づいている、ということです。STACK クラスが総称的に制約されているという事実は、私たちに、どんな単一の型とそのすべての子孫を保持する単一の STACK を宣言することだけを許しており、同時にすべての型を保持するスタックの宣言を許しているのではありません。
これは危険ですか? まったくそんなことはありません。前に述べたように、より古いプログラミング言語は、この動作を、スタック上へ置かれるべきであった他の見知らぬデータ構造を型変換することによって模倣していたでしょう。Eiffel は、非常に強力で、しかもしっかりした型付けシステムを持っています。このスタック上に置かれるすべてのオブジェクトは、あるいは、あなたが生成する他のどんな総称構造も、システムの存在中のそれらの識別性を残しています。もし私たちが TRAY と NEW_TRAY を tray_stack 上に置くべきであったならば、
tray_stack : STACK [TRAY] t : TRAY; new_t : NEW_TRAY; ... tray_stack.push( t ); tray_stack.push( new_t );オブジェクト t と new_t は、いまだにその識別性を残しています。つまり、t は、いまだに TRAY であり、new_t は、いまだに NEW_TRAY である、ということです。
Eiffel は、プログラマに、潜在的に危険な適合を強いる、オブジェクトの型を変換することを許すファシリティを一切提供しません。これらの危険な実践<practice>は、Eiffel が、オブジェクト パラダイムの完全な潜在能力についてのこの恩恵を安全に利用するために、この機構を提供しているので、もはや必要ではありません。
もし使用に気付くことができたならば、同じく、次のように、多重にパラメータ化されるクラスを宣言できたでしょう。
class MULT_PARAM [T, P] feature ... end -- MULT_PARAMこのことは、次のように、MULT_PARAM の宣言に、二つの型のパラメータを提供することを単に要求します。
some_entity : MULT_PARAM [VEHICLE, APPLE]この能力の潜在的な使用は、オブジェクトのいくつかの型を保持するコンテナの構造を伴うことであったかもしれません。実例として、家庭用娯楽キャビネットは、ビデオテープ、カセットテープ、CD、レーザーディスクなどを収容します。
いままで、私たちは、どんな型の別のクラスによってでもパラメータ化されうる総称構造を宣言する能力を目撃してきました。必要性と同じくらいより単純な総称クラスのバージョンは、制約された総称クラスです。制約された総称クラスは、特有な型へ適合している型がインスタンス化のための単なる候補である、ということを宣言します。たとえば、
class BIN_TREE [T -> COMPARABLE] ... end -- BIN_TREEこのクラスは、BIN_TREE を宣言するために、提供した実総称パラメータが COMPARABLE クラスへ適合していなければならない、ということを宣言します。形式パラメータ T の後にある -> という新たな構文は、BIN_TREE はいまや、COMPARABLE 型へ適合する実体を受け入れることが総称的に制約される、ということを述べています。
おそらく読者がご存知のように、二分木は、その要素への素早いアクセスによって特徴づけられる構造です。しかし、二分木がこの能力を持つ理由は、その要素が整列された順序で存在するからです。このことは、その要素が木からのどんな二つの要素を与えた方法で配置されており、私たちは、それらを比較でき、他のものの前か後のどちらに並べるかを決定できる、ということを暗に意味しています。私たちは、ORANGE といったオブジェクトを二分木に置くことはできなかったでしょう。もちろん、ORANGE A が ORANGE B よりも大きいか小さいかを判断することを可能にする、何らかの特徴が無い限りは。
制約された総称は、上記で示したように、実総称パラメータが、総称クラスの意味論を有効なままとすることを要求される特定の特徴を持つ、ということを保証するという目的のために働きます。もし私たちが次のようなクラスを宣言すべきであったのならば、
class ELEMENTS inherit COMPARABLE end; end -- ELEMENTS私たちは、次のようにも宣言できたでしょう。
class AVL feature tree : BIN_TREE [ELEMENTS] end; end -- AVLこれは、ELEMENTS を要素として受け入れる木と呼ばれる実体を生成します。BIN_TREE が正しく働くことを要求されるクラス COMPARABLE の特有な特徴は、BIN_TREE が要素が木のどこへ置かれるべきかを判定する際に利用する、実際の比較ルーチンです。これ無しでは、BIN_TREE は、正しく実行できなかったでしょう。
総称の機構を用いて、いまや、総合的な安全性、信頼性、そして抽象性を持った、総称的なデータ構造を完全に(あるいは部分的に)生成することが可能になります。
[ 目次 ] [ イントロ | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Copyright ] [ html (full) | ps | pdf ]
[ Eiffel Liberty | GUERL | sOOap ]