[ Eiffel Liberty | GUERL | sOOap ]
by
Alan A. Snyder
and
Brian N. Vetter
1 - 基本的な概念と構文
1.0 クラスとオブジェクト
1.1 参照
1.2.1 拡張オブジェクトの宣言
1.2 拡張型
1.3 特徴
1.3.1 属性 - 変数と定数
1.3.1.1 唯一な属性
1.3.1.2 定数
1.3.2 ルーチン - 手続きと関数
1.3.2.1 生成ルーチン
1.3.3 局所的属性
クラスは、本質的に、オブジェクトを作り出すことを通じて、極度にモジュール的な抽象データ型の型枠<template>になります。つまり、クラスは、そのクラスに関連した、一式の操作(ルーチン)とデータの実体とを持つ構造体<structure>です。Eiffel では、あらゆる実体は、クラスの実例<instance> -オブジェクト- です。標準データ型の一部として用意されている INTEGER と REAL でさえ、実際のクラスです。これが意味しているのは、もしかしたら INTEGER から継承して、新たなクラス NEW_INTEGER(もしそういうクラス名を望むなら)を作ることができる、ということです。
クラスとオブジェクトとの違いは、伝統的言語における型と変数との違いに似ています。Eiffel には、二つのタイプのオブジェクトがあります。それは、参照と拡張オブジェクトです。それらは、話を限定すれば、オブジェクト「へのポインタ」とオブジェクト自体、であると考えこともできます。では、もう少し近づいて、これらのオブジェクトを調べてみましょう。
Eiffel で宣言するオブジェクトのデフォルトのタイプは、参照です(本質的にはポインタですが、ポインタに付き物の危険は伴いません)。これは、次の例で示すように、新しいオブジェクト上の生成<creation>ルーチンを呼び出すことによって達成します。
x : SOMECLASS; !!x.make; -- SOMECLASS の生成ルーチンを呼ぶ x.some_routine; -- some_routine は、SOMECLASS のメンバーです最初に、x が、SOMECLASS というクラスの実例として宣言しています。Eiffel で宣言する実体は、大部分の手続き型言語に似ています。つまり、識別子の後にコロン(:)が続き、その後にクラスの名前が続きます。
その次の行は、x を実例化するために、x に関して二重の感嘆符(!!)の演算子を使用しています。make は、SOMECLASS 内で定義している、オブジェクトの初期化を遂行する生成ルーチンです(その宣言は、ここでは示しません)。加えて、このルーチンは、オブジェクトを使用する準備のために、ほかの動作をするかもしれません。make ルーチンにおいて、x のための記憶域を割り付けるために、このルーチンによるどんな明示的な呼出しも必要ありません。!! 演算子は、make ルーチンが安全に初期化を遂行できるあいだに利用可能とする記憶域を保証するように配慮しています。
x 上の生成(あるいは他の)ルーチンを呼び出す前にオブジェクトを参照しようとすれば、結果として、例外が起きるでしょう。上の例において、x.some_routine は、!!x.make の後に呼ぶことだけができます。
!!x.make が呼ばれる前に、x は、次のように見えました。
![]()
この時点で、x は宣言されていましたが、参照タイプであったから、自動的なデフォルトの初期化は、x を Void に設定します。これは、話を限定すれば、Modula-2 や C/C++ における NIL または NULL に似ています。
!!x.make というステートメントの後では、x は、いまや次のように見えます。
![]()
もしクラスが、そのすべての実例を拡張するように定義しているならば、そのクラスのタイプで宣言されたオブジェクトは、それが宣言されるやいなや、すぐに使われる用意ができています。
たとえば、型 INTEGER のオブジェクトを宣言してすぐに使用してよいのは、INTEGER が拡張されたクラスだからです。拡張オブジェクト(拡張クラスとして宣言している)は、使う前に !! 演算子を呼び出すことを要求しません。それは、暗黙的に、オブジェクトを宣言するとき、自動的に呼ばれます。このことは、オブジェクトへの参照とオブジェクト自体との間の選択(変数へのポインタか、まさに変数か)に柔軟性を与えます。
そのすべての実例が拡張されるようなクラスを宣言するためには、クラス宣言の前に expanded キーワードを使用してください。
expanded class INTEGER feature ... end -- INTEGERもし私たちが INTEGER クラスのオブジェクトを実例化したいのであれば、単純に、次のようにそれを宣言します。
x : INTEGER; -- この時点で、x は、既定値である 0 になっています。 x := 1; -- これで、x は、値 1 になります。自動的な初期化の部分が、オブジェクト x に値 0 を持たせます。Eiffel では、すべての実体は、既定値へ初期化されます。基本型に対応する既定値は、次の通りです。
INTEGER = 0 REAL = 0.0 DOUBLE = 0.0 CHARACTER = Null_char BOOLEAN = False reference = Void上記の REFERENCE という型は、実クラスの型ではありません。むしろそれは、クラスの型が上記の一覧のどれでもない、ということを明示します。つまり、もしクラスが宣言され、expanded キーワードが先行して書かれていないのならば、そのクラスの型のすべてのオブジェクトは参照になります。そのため、宣言するやいなや、参照そのものは、デフォルトで Void へ設定されます。
拡張型へ話を戻しましょう。図によって、もし私たちが 拡張された INTEGER である x を表現したいのならば、それを次のようにできました。
![]()
上図は、宣言した時点での x を表現しています。
オブジェクトへの参照が必要とされない時が来たかもしれません。オブジェクト自体は、より適したものとなっていますが、(expanded キーワードを経由して)クラス自体を通して利用できません。この場合、オブジェクト自体は、拡張されたものとして宣言することもできます。その効果は、あたかもクラス自体が、上述したように、拡張されたものとして宣言したのと同じです。拡張されたオブジェクトの場合、私たちは、クラス自体が拡張として宣言されているか、あるいは拡張ではなく宣言されているかということについて、分かっていることもあるし、そうでないこともあります。しかしながら、私たちが分かっているのは、オブジェクトが拡張される、ということを私たちが願っていることです。もしこれがその事例ならば、私たちは、次のようにすることもできます。
x : expanded SOMECLASSこの場合、x は、型 SOMECLASS ですが、(!! 演算子で)初期化しなければならない参照である代わりに、SOMECLASS にとって必要なすべての記憶域は、宣言によって割り付けられます。それから、SOMECLASS のそれぞれの属性は、その想定された既定値へ自動的に初期化されます。
特徴は、オブジェクトが格納してもよいデータと、オブジェクトに関して実行してもよい操作です。Eiffel には、属性とルーチンという、二つのタイプの特徴があります。Eiffel における特徴という用語は、正確に、データと操作を指します。特徴は、クラスが何を持ち、何をできるか、ということを記述することを許します。つまり、'has-a' 関係、あるいは 'can-do' 関係をサポートします。実例として、CAR は、エンジンを「持つ」('have-a' engine )でしょう。COMPUTER は、CPU を「持ちます」('has-a' CPU)。また、CAR は、前進することが「できます」。COMPUTER は、計算することが「できます」。
属性は、その宣言がクラス内に現れるオブジェクトです(集合体<aggregation>)。属性は、参照クラスであっても、INTEGER といったクラスの拡張クラスであってもかまいません。次にあるのは、属性の特徴の並びの例です。
feature count : INTEGER; precision : DOUBLE; last_key : CHARACTER; icon_selected : BOOLEAN;
Eiffel は、列挙型を実装するための機構を提供していません。その代わり、そのオブジェクト指向の性質を強力に保持しながら、列挙型の効果を模倣する能力を提供します。この能力は、unique(唯一)というキーワードで明白にします。例は、次のようになります。
class FSM feature state_1, state_2, state_3 : INTEGER is unique; ... endunique として宣言してもよい実体の型は、INTEGER だけです。
unique な INTEGER となったものは、そのクラス内で unique として宣言した、他のすべての INTEGER のものとは異なるということが保証されます。ここで、state_1、state_2、そして state_3 が unique であるのは、それらが、それぞれ異なる INTEGER の値へ割り当てられることになるからです。unique な属性は、本質的に、その値をコンパイラが選ぶ定数です。読者にとってこれが意味していることは、本質において、(INTEGER の値を意味のある識別子へ割り当てることで)列挙の機能性<functionality>を実装しているが、純粋なオブジェクト指向の構造をそのまま残している、ということです。
クラス内のどんな唯一な属性も、それぞれの正当な宣言がその直前のものよりも一つ大きく、それぞれの実体もまた整数であるといった数値を割り当てられることになります。上記の場合に、もし state_1 に値 1 を割り当てていたならば、state_2 と state_3 は、予期したとおり、2 と 3 を割り当てられていたでしょう。state_1 に値 5 を割り当てることも可能であったでしょう。この場面では、state_2 と state_3 は、予期したとおり、6 と 7 を割り当てられることになりました。
Eiffel では、定数は、必ず、関連した型を用いて宣言します。定数として宣言しうるオブジェクトの型は、次のものだけです。
INTEGER REAL DOUBLE BOOLEAN CHARACTER ARRAY最も伝統的な言語においても利用しうる基本型があります。いま、私たちは、どのように実体を定数として宣言するかをご覧に入れましょう。
feature Max : INTEGER is 100; Pi : REAL is 3.14; Better_pi : DOUBLE is 3.14159; Default_save_mode : BOOLEAN is False; Menu_option : CHARACTER is 'M';
ルーチンは、そのクラスによって利用されるアルゴリズムを提供するクラスに属している関数と手続きです。それらは、そのクラスへのデータの入力や出力を容易にし、オブジェクトの状態に基づく値を計算することもできます。属性と同じように、ルーチンは、feature キーワードに続けて宣言することもできます。
たとえば、次のクラスは、摂氏の温度を華氏へ変換します。それは、初期の温度を設定する手続き set_temperature と、変換された値を返す関数 convert を利用します。
class CONVERTER feature temperature : DOUBLE; conversion_factor : DOUBLE is (5/9); set_temperature ( c : DOUBLE ) is do temperature := c; end -- set_temperature convert : DOUBLE is do Result := temperature * conversion_factor + 32; end -- convert end -- converterクラス CONVERTER には、四つの異なるタイプの特徴があります。一つ目の温度は、型 DOUBLE の実例です。それは、プログラムの実行中に変更してもよい値を持つ変数の属性です。
二つ目の conversion_factor(変換係数)は、クラス DOUBLE の実例であり、(5/9) という値で初期化されます。このキーワードを用いることは、上述したように、conversion_factor を定数の属性にすることを引き起こします。
三つ目の set_temperature(温度を設定する)は、値を返さないルーチンです。それは、型 DOUBLE である一つのパラメータ c を取ります。このキーワードは、その識別子が意味のある値を割り当てようとしていることを示しており、そして、一組の do...end は、これがルーチンであることを述べています。
最後の convert は、パラメータを持たず、型 DOUBLE の値を返す関数です。それは、変換した値をキーワード Result(結果)へ割り当てます。Result は、Eiffel にとっての返却<pass-back>機構です(オブジェクト)。その値が関数終了時に現れたら何でも、呼び出し元のルーチンへ返すものです。
前節で注意したように、参照のオブジェクトは、そのクラスの生成ルーチンによって初期化する必要があります。ルーチンは、それを、creation キーワードに続く一覧において、生成ルーチンとして明示します。それからなら、このルーチンは、初期化を提供する(!!) 演算子を用いて、オブジェクト上で呼び出してもかまいません。下の例では、make は、生成ルーチンとして明示しています。
class CONVERTER creation make; feature temperature : DOUBLE make is do temperature := 98.6; end -- make ... end -- CONVERTEREiffel では、生成ルーチンは、どんな他のルーチンともまったく異ならずに扱われます。つまり、それは、オブジェクトの生成中に呼び出してもよいし、あるいは、そのオブジェクトの存在中のどんな時に呼び出してもかまいません。上記で与えられたテキストにおいて、私たちは、make を、もし私たちが temperature の属性に 98.6 度を設定したいと思ったのなら、クラス CONVERTER 上のオブジェクトに関して、どんな時に呼び出してかまいません。このことは、クラス CONVERTER のオブジェクトに対して、記憶域を割り付けないでしょう。(!!)の生成演算子だけが、記憶域の割り付けを行うことになります。いま、私たちは、クラス CONVERTER の実例、実例に対するウィジット<widget>、を宣言し、それを次のように生成することもできます。
... widget : CONVERTER; !!widget.make; ...デフォルトでは、CONVERTER が拡張クラスとして定義されておらず、そしてウィジットが CONVERTER の拡張されたバージョンとして宣言されていないので、ウィジットは、CONVERTER オブジェクトへの参照になります。ウィジットを使用する前に、 それは、(!! 演算子を使用して)生成され、make ルーチンへの呼出しを用いて初期化されます。
ルーチンと関数は、計算における補佐を補助するために、あるいは何らかの他の理由のために、ときどき、局所的な(そして一次的な)記憶域を必要とすることもあります。局所的変数は、Eiffel に限らず、プログラミング言語にとって目新しいことは何もありません。局所的変数は、実際には、(すべての変数がそうであるように)オブジェクトです。局所的な実体は、次のように宣言することもできます。
... convert : DOUBLE is local loc : DOUBLE do loc := temperature * conversion_factor + 32; Result := loc; end -- convert ...この場合に、loc は、convert 関数内の局所的な変数です。それは、この関数が実行しているときに存在するだけです。それは、変換のための呼出しの間の値を保持しません。
もし局所的変数の代わりに、局所的な定数を必要とするならば、同じ構文と意味論<semantics>が、1.4.1.3節で記述したように、定数を宣言するための構文と意味論として適用されるはずです。
[ 目次 ] [ イントロ | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Copyright ] [ html (full) | ps | pdf ]
[ Eiffel Liberty | GUERL | sOOap ]