オブジェクト

2012.02.12

Lemma.pmの機能拡張

■ オブジェクト指向プログラミング

Lemma.pm(参照)の拡張性を上げようとするなら「Object Oriented」な、つまり「object(データと手続きが一体化した対象?)」「oriented(本位の?)」プログラムに作り替える必要がある。とりあえずこの種のプログラムには「クラス class」「オブジェクト object」「メソッド method」の三要素を揃えなくてはならないらしい。

Perl モジュールの場合、幸いにも二つの要素はすでに揃っている。「パッケージ package」がクラスで「サブルーチン sub routine」がメソッドである。呼び方を変えるだけでなんとかなりそうだ。しかし「(三要素の中でもっとも重要な)オブジェクトとは何か?」については説明が難しいようでハッキリしない。

以下「オブジェクトとは何か」について考察しつつアレコレと試行した際の覚え書きを記す。

■ コンストラクタメソッド

オブジェクトを生み出すパートをモジュールに組み込まなくてはならない。このような機能を果たすサブルーチンを constructor と呼ぶそうである。

sub new {
$class = shift;
$data_structure= {
option => undef,
surface => undef,
lemma => undef
};
bless $data_structure,$class;
}

上記のサブルーチンを利用して実際にオブジェクトを生成するには Lemma.pm の使用を use で宣言した上で CGI スクリプト側で以下のように記述する。

$object = Lemma->new();

これによって $object には「Lemma = HASH(0x801180)」というようなスカラー値(つまり1つの値だけ)が入る。等号の後ろの値は ref 関数が自動的に取得した戻り値(この場合はHASHでデータ型を示している)に「リファレントの内部メモリアドレスを表す固有の16進数」が追加されたものである。等号の前にあるのはパッケージ名でありクラス名でもある Lemma のことで、等号で両者が結びつけられている。

上の例で明示的には使用していない ref(リファレンス)関数が働いたのは「->」つまり「アロー演算子」の効果である。だがアロー演算子を使わなくても同様の値は得られる。

#$object = Lemma::new();
$object = Lemma::new(Lemma);

コメントアウトした方を実行するとクラス名(等号の前)が main に変わってしまう。main は何も指定されなかったときのパッケージ名である。そうなってしまった原因は簡単に説明がつく。

まず Lemma に属する new というサブルーチンを引数なしで実行すると $class =shift の値が空になる。この式は $class = shift(@_) の省略形で、@_ はサブルーチンに与えられた引数を格納する特殊配列変数である。$class が空だと bless $data_structure,$class の意味が bless $data_structure とだけ書いたのと同じになってしまい bless関数の役割である「特定のクラスに所属するという印をつける」際の「特定のクラス」が main にされてしまうのである。

■ リファレンス

前項で出て来た「リファレンス」「リファレント」についてオブジェクトとの関係を考えつつ、少しまとめる。

>> 『オブジェクト指向Perlマスターコース』

変数や、サブルーチン、値に間接的にアクセスすることが必要になる場合がある。このような場合、その名前を直接使用するのではなく、汎用的な方法で参照することが重要である。このため Perl では、リファレンスという特殊なスカラデータ型が提供されている。リファレンスは、伝統的な禅の概念である"月を指す指"(finger pointing at the moon)に似ている。指(リファレンス)は、月(変数、関数、または値)ではなく、単にそれを指す手段でしかない。既存の変数または値へのリファレンスを作成するには、単項演算子 \ を使用する。この演算子は、変数または値を取得し、そのリファレンスを返す。さらに、この元の変数または値を、リファレンスが参照するリファレントという。(p.51)

前出の例で $object に格納された値は「クラス名」と「リファレンス」を等号で結んだものだと考えられる。このときの「リファレント」は $data_structure の内容である。この三者は bless 関数の働きで関連付けられている。なお単項演算子 \ を用いたリファレンス等々、リファレンス機能全般については煩雑なのでここでは触れない。

ここまでの考察からすると、どうやらオブジェクト指向プログラミングにおけるオブジェクトというのは「クラス名と関連付けられたリファレンス」のことらしい。要は「リファレンス」なのである。

確かにコンストラクタである new() が Lemma = HASH(0x801180) というスカラー値を生み出しているのだから、それをオブジェクトと呼んで悪い理由も見当たらない。つまり「月を指す指(月のリファレンス)」こそがオブジェクトであって「月(月のリファレント)」がオブジェクトなわけではないということである。

ところがラクダ本ではちょっと違うニュアンスで次のように書いてある。

>> 『プログラミング Perl 改訂版』

オブジェクト(object)とは、振る舞い(behavior)の集合を持ったデータ構造である。(略)オブジェクト(object)は単にリファレンスによって指されている「物」で、自分がどのクラスに所属しているかを知っている。(pp.331-332)

この説明では「データ構造」つまりリファレントこそがオブジェクトであるように読める。そして「どのクラスに所属しているかを知っている」という言い方に含まれるニュアンスが今ひとつわかりにくい。

もしかするとこれは、ソシュールのいうシニフィエとシニフィアンがシーニュの両面であるという図式や、『The Meaning of Meaning』で図示された Symbol と Referent を媒介する Reference という構図を工学的に(言語学以前の名詞論的な考えを引き摺りながら)説明しようとしていることに対する違和感なのかもしれない。少なくとも「オブジェクト」は(その名前とは異なって)「物」ではない何かを意味しているように見える。

ソシュールに倣ってリファレンスをシニフィアン(たとえば「ネコという文字列や音声」)に当てはめ、リファレントをシニフィエ(たとえば「ネコという概念」)と考えてみれば、この両者は常に同時に存在する(エラーがない場合、レファレンスとレファレントの一方だけで存在するという状態はない)ことになる。クラスに対して bless されたデータ構造がレファレンスとレファレントの両面を持つ様になったとき、その状況全体をオブジェクトであると理解するのは可能かもしれない。

またオグデンとリチャーズが示した方式に則って「Lemma = HASH(0x801180)」というスカラー値を Symbol とし、$data_structure の内容を Referent、この両者を結びつける機能や視点がオブジェクトとしての Reference だとみなすこともできる。

上記のいずれの考え方に立つにせよ、「オブジェクト」とそれに関する「リファレンス」「リファレント」に該当するものはすべて「オブジェクト」と呼ばれる可能性に留意が必要である。これは言語行為に際して日常我々が取る態度と共通している。

■ オブジェクトとメソッドの関係

オブジェクトはメソッドを利用できる。各メソッドはその目的に応じてオブジェクト(のリファレント)に改変を加える一連の工程である。ここで言及するようなメソッドのことはコンストラクタのようなタイプ(「クラスメソッド class method」)と区別して「インスタンスメソッド instance method」と呼ぶようだ。

両者の差は引数を格納する特殊配列 @_ の第一要素がクラス名かオブジェクトリファレンスか、という点に現れる。後者のケースがインスタンスメソッドである。この種のメソッドはCGI 側では以下のように書いて利用する。

$object->getopt_argv("-f","EOS");

getopt_argv() はサブルーチンでありメソッドでもある。オプション機能の使用を示す文字列を引数として持つ。オプション機能の指定を取得してオブジェクトの option 項目を変化させるという目的のメソッドである。これを受けるモジュール側は以下の通り。

sub getopt_argv {
$object = shift;
while (@_){
  $opt = shift(@_);
  if($opt eq "EOS"){
   $option1 = 1;
  }elsif($opt eq "-f"){
   $option2 = 2;
  }
}
$option = $option1 + $option2;
$object->{option}=$option;
}

$object = shift で $object に格納される値は CGI 側の $object の値と同じである。これは $object->getopt_argv() のようにアロー演算子を使うと特殊配列変数 @_ の先頭要素にオブジェクトリファレンスが格納されることによる。

$object->{option}=$option;

上の例のように最後のパートでもアロー演算子を使っているが、これまでと役割が少し違っている。この部分ではリファレント全体ではなく option => undef というオブジェクトリファレントの一部分だけを参照し、それを上書きして option => $option となるようにしている。上記の例では EOS と -f はどちらも引数に入っているので while ()以下の処理を経て $option は 3 となり、リファレントの中身は option => 3 というように変えられる。

このメソッドを使用した結果(生じた変更)は別のメソッド(=サブルーチン)からでも次のように書けば確認できる。

$option = $object->{option};

ここでも $object はオブジェクトリファレンスを格納したものとする。$option には 3 という数値が入る。

このやり方でサブルーチン間での値のやり取りができるようになるのだが、同じことはグローバル変数を使えばもっとずっと簡単にできる。オブジェクトがどうのこうのと面倒なことする代わりに our $option を用いれば良いのである。これに対してオブジェクトを生成する利点は何だろうか。

$option = $object->{option};

上記のようにしてサブルーチン間での場合と全く同じ書式で CGI 側からでも簡単に参照できる点がまず指摘できる。グローバル変数は実際にはパッケージ内でだけ制限を受けない変数であってモジュールとCGIの双方でグローバルに振る舞う変数ではない。

$object->{surface}="This is a pen.";

またオブジェクトを使えば上記のようにメソッドからでもCGI側からでも全く同じ記述で自由にオブジェクトの該当箇所を書き換えられる。つまり参照したり書き換えたりする需要の大きい重要なデータ項目をオブジェクトリファレント(のハッシュキー)として集約しておけば見通しや利便性が格段に向上すると期待できるのである。

■ sparse_tostr メソッド

オブジェクト不使用版 Lemma.pm にもあったサブルーチン sparse_tostr で $lines に格納された文字列をレンマ化処理する際の CGI 側書式は以下のように変わる。

$result = $object->sparse_tostr($lines);

sparse_tostr の処理結果を return関数で CGI 側に戻すことをやめて、それをオブジェクト(の lemma => undef)に渡すよう改変してしまえば上の書式は次のように変わる。

$object->sparse_tostr($lines);
$result = $object->{lemma};

二行になったので煩雑化したようにもみえる。だが、この方式にすると sparse_tostrメソッドがレンマ化以外にも様々な処理を行なえるように機能拡張されたときに利便性が増すと期待できる。処理結果を適宜取捨選択して得ることが容易になるのである。

たとえば「記号等々の不要な要素は取り除き文末マーカーは付けた上でレンマ化しない状態の文字列を得たい」というような要望があったとする。

大部分の作業工程が既存の sparse_tostrメソッドと同じであると予測できるので、この作業専門のメソッドを作るのは無駄でありプログラム保守管理の点からも(複数の散在する似たようなコードを直して回るという状況が起こり得るのは)好ましくない。そこで sparse_tostr の中でこの処理も同時に行なってしまい、その結果をオブジェクトに渡しておくようにするのである。そうしておいて必要があれば $result = $object->{surface} などとして引き出せば良い。

雑感

■ クラス、オブジェクト、メソッドの数

今回扱った例だとクラスは1個、オブジェクトも1個、メソッドは複数ということになる。クラスについてはモジュール1個につき1個というのが普通であるように思える。メソッドについては、コンストラクタメソッドは1個が普通かもしれないが、インスタンスメソッドは大抵複数用意されているはずである。そして、ここでもオブジェクトについてはわかりにくい。

コンストラクタメソッドが1個であってもオブジェクトは多数生成することができる。上の例でもCGI側に以下のように書けば4個の異なるスカラー値が表示される。

print Lemma->new();
print Lemma->new();
print Lemma->new();
print Lemma->new();

これを例えば $object1 から $object4 までの4個の変数に格納した場合、$object1->{surface}="There is no moon tonight." としても $result=$object2->{surface} の$result は undef のままである。同じようなデータ構造を持つ複数のレファレンスとレファレントのセットが作られていて、それらのデータ項目は他のセットが持つそれと相互に無関係なのである。

先述したレファレンスについての引用で「finger pointing at the moon」と finger が無冠詞だったのもこうした事情を反映しているのかもしれない。だが詳しく考察していくと英語における冠詞の問題なども混じってきて収拾がつかなくなるので、これについては深入りしないこととする。

■ 参考サイト

ネット上では以下のサイトを参考にした。

>> ++ KUROITA ++ CGI and Studying

少なくとも私が見た中でまともに「(Perl を使った)オブジェクト指向プログラミング」が理解できるように書いてくれていたのはここだけだったように思う。深く感謝したい。ありがとう&幸あれ。

▼研究関連メモ目次へ戻る

Copyright(c)2005-2012 ccoe@mac.com Allrights reserved.