Tips集 | 石原進のホームページ | 水野研究室 | 情報科学科 | 情報学部 | 静岡大学

OTclチュートリアル(Version 0.96, September 95)

訳・石原進(Feb 1 2000,修正 Jul 3 2001)

このチュートリアルは,読者が既にオブジェクト指向プログラミングに親しんでいることを前提として,OTcl によるプログラミングを速やかに開始できるように用意されたものである。ここではリファレンス Objects in OTcl や Classesin OTcl から得られる言語の多くの詳細は無視している。また,C API や autoloaded クラスの書き方についても言及していない。

C++ との比較

C++ プログラマには OTcl のオブジェクト指向プログラミングは最初奇妙に映るだろう。ここでは読者が OTcl に適応できるようにいくつかの違いを示しておこう。

OTcl によるプログラミング

我々のアプリケーションではたくさんのベーグル(パン)とつき合わなければな らないとしよう。まずは,Bagel クラスを作るところから始めよう。
    % Class Bagel
    Bagel
これでベーグルを作って,それを info コマンドで追跡できるようになった。
    % Bagel abagel
    abagel
    % abagel info class
    Bagel
    % Bagel info instances
    abagel
もちろんベーグルはまだなにもしない。それらは,焼かれたのかどうか記憶しているべきだ。set コマンドによってインスタンス変数を作り,アクセスすることが可能だ。すべてのインスタンス変数は C++ でいうところの public だ。もう一度 info コマンドを使ってこれらのことを見てみることにしよう。
    % abagel set toasted 0
    0
    % abagel info vars
    toasted
    % abagel set toasted
    0
しかし,我々が本当に望むのは,ベーグルが最初は焼かれていない状態にあることだ。これは Bagel クラスに init instproc を加えることで達成できる。一般的に,新しいオブジェクトが初期化されるべき場合には,それらのクラス用に init instproc を書けばよい。
    % Bagel instproc init {args} {
      $self set toasted 0
      eval $self next $args
    }
    % Bagel bagel2
    bagel2
    % bagel2 info vars
    toasted
    % bagel2 set toasted
    0
ここで述べておくことがいくつかある。オブジェクトを作る段階で,シス テムは,それらが生成され(割り当てられ)た直後に,init が呼ばれるよ うに手配する。instproc メソッドは Bagel クラスで使えるようにメソッ ドを追加した。このメソッドの名前は init なので,システムは新しいベー グルが作られた場合に,それを見つけ出し,呼び出すのである。

init instproc にはまたいくつかの面白い特徴がある。next の呼び出しは init メソッドには典型的なものであり,すべての継承された init メソッ ドを派生した init メソッドに結びつけるものである。これに関しては後 で詳しく述べる。self という名前の変数はメソッドが呼び出された時にセッ トされ,それが動作中のオブジェクトの名前が入っている。この例の場合, bagel2 が入っている。これは C++ における this に似ており,そのオブ ジェクト上の他のメソッドやオブジェクトクラスを通して継承されたメソッ ドを使うために使用される。また,興味深い二つの特別な変数 proc と class がある。

いま,我々のベーグルは,init を書く前につくった最初のものを除き,そ れが焼かれたかどうかを記憶している。最初のものを壊して,再び始める ことにしよう。

    % Bagel info instances
    bagel2 abagel
    % abagel destroy
    % Bagel info instances
    bagel2
    % Bagel abagel
    abagel
これで,ベーグルにそれらをトーストするためのメソッドを加える準備が できた。インスタンスによって使用されるためにクラスに保存されている メソッドは instproc と呼ばれる。それらは普通の Tcl の手続き (proc) と同様に,引数リストと本体をもっている。次に toast instproc を示そ う。
    % Bagel instproc toast {} {
      $self instvar toasted
      incr toasted
      if {$toasted>1} {
        error "something's burning!"
      }
      return {}
    }
    % Bagel info instprocs
    init toast
toasted 変数のセットは別として,toast instproc は instvar メソッド の使用例を示している。instvar メソッドはインスタンス変数の宣言とそ れにローカルなスコープを与えるために使用される。インスタンス変数 toasted は set メソッドによって以前に初期化されていたが,ローカル変 数 toasted を通して操作することができる。

我々はベーグルの toast instproc をシステムによって提供されている info や destroy instprocs と同じように呼び出した。つまり,ユーザ定 義のメソッドとシステムメソッドには違いはないのである。

    % abagel toast
    % abagel toast
    something's burning!
これで我々はスプレッドをベーグルに塗って味わうことができるようになっ た。トッピングされたベーグルはもちろん,もしトッピングされていない ベーグルがあったら,トッピングできるベーグルを別のクラスとして作り たくなるだろう。これらの二つのクラスを使って「継承」に関して調べて みよう。まずは,Bagel から継承される新しいクラス SpreadableBagel を 作ることから始めよう。
    % Class SpreadableBagel -superclass Bagel
    SpreadableBagel
    % SpreadableBagel info superclass
    Bagel
    % SpreadableBagel info heritage
    Bagel Object
    % 
info メソッドのさらなるオプションによって SpreadableBagel が本当に Bagel から継承されたものであるか,また Bagel もが Object から継承さ れているかどうか判定できる。Object にはすべてのオブジェクトの基本的 機能が盛り込まれている。これらの機能は新しいクラスがデフォルトで継 承する。つまり,Bagel は Object から直接継承をしており, SpreadableBagel は Bagel 経由で間接的に Object を継承している。

"-superclass" によるクラス生成 のための文法に関しては,さらなる説明 が必要だ。まず,読者はクラス生成 (create) 以外のすべてのメソッドが どうしてオブジェクト名の後のメソッドの名前でよばれるのか困惑してい るだろう。その答えは,クラス生成 (create) は,他のメソッドがたとえ 見つからなくてもシステムが感知しないメカニズムで呼ばれるからという ことだ。これは良く知られたウィジェットライクなクラス生成文法を提供 するために行われている。しかし,あなたが望むなら create を明示的に 呼び出すこともできる。

次に,オブジェクトの初期化の段階で,引数の各ペアは,対応する引数と ともにオブジェクトを呼び出すために(名前の頭にダッシュ(-)がついた) 手続きの名前に書き換えられる。この初期化機能は Object クラスの init instproc によって行われる。これが Bagel の init instproc が next を 呼ぶ理由である。以下の二つのコードの切れ端は(戻り値を除いて)同じ 意味をもつ。簡略型の記法が通常用いられるものであり,長い方の書き方 は簡略型の記法で実際に行われることを示している。

    % Class SpreadableBagel
    SpreadableBagel
    % SpreadableBagel superclass Bagel

    % Class create SpreadableBagel
    SpreadableBagel
    % SpreadableBagel superclass Bagel

    % Class SpreadableBagel -superclass Bagel
    SpreadableBagel
この関係を理解すれば,オブジェクトの生成に何も特別なことはないと分かるだろう。例えば,ベーグルのサイズを byte (いわゆるバイトではなく噛む回数)で指定するようなオプションを加えることもできる。
    % Bagel instproc size {n} {
      $self set bites $n
    }
    % SpreadableBagel abagel -size 12
    abagel
    % abagel set bites
    12
我々は,SpreadableBagel にトッピングを塗り付けるためのメソッドを加 えなければならない。それには現在のトッピングのリストも必要だ。常に 空のトッピングのリストから始めるということにするのなら,init instproc も必要だ。
    % SpreadableBagel instproc init {args} {
      $self set toppings {}
      eval $self next $args
    }
    % SpreadableBagel instproc spread {args} {
      $self instvar toppings
      set toppings [concat $toppings $args]
      return $toppings
    }
ここで init メソッド中での next の使い方について,もう少し説明しよ う。SpreadableBagel もベーグルであり,それらの toasted 変数もゼロに 初期化される必要がある。next メソッドの呼び出しによって,継承木を上 にたどった次のメソッドが見つかり,呼び出される。これは CLOS の call-next-method と同様の機能を提供している。

この場合,Bagel クラスの init instproc が見つかり,呼び出される。 eval は args の中にある引数リストをフラットにするためにするためだけ に用いられている。Bagel の init instproc 中で next が再び呼ばれた場 合,Object の init メソッドが見つけられ,呼び出される。それはその引 数を手続き名と引数値のペアに書き換えて,順番にそれぞれを呼び出して, すべてのオブジェクトのオプション初期化機能を提供する。init instproc 中で next の呼び出しを忘れてしまうと,オプションの初期化が行われな い。

ベーグルに taste instproc を加えよう。その機能を二つのクラスに分け て,next によって結合することにする。

    % Bagel instproc taste {} {
      $self instvar toasted
      if {$toasted == 0} then {
        return raw!
      } elseif {$toasted == 1} then {
        return toasty
      } else {
        return burnt!
      }
    }

    % SpreadableBagel instproc taste {} {
      $self instvar toppings
      set t [$self next]
      foreach i $toppings {
        lappend t $i
      }
      return $t
    }
    
    % SpreadableBagel abagel
    abagel
    % abagel toast
    % abagel spread jam
    jam
    % abagel taste
    toasty jam
もちろん,ゴマやオニオン,ケシ,他のベーグルの大群が次々にやって来 たら,我々の方法を拡張しなくてはいけない。我々はインスタンス変数に よって味付けを見張ることができたが,これは適切ではないだろう。味は ベーグル生来の特性だが,他の味にも影響を与える。オニオンベーグルに ジャムは塗らないだろう? クラス階層構造を作る代わりに多重継承によっ て,味のクラスの mixn を作ろう。これらはベーグルやその他の混ぜあわ される食材の味の依存特性を与えるものだ。
    % Class Sesame
    Sesame
    % Sesame instproc taste {} {
      concat [$self next] "sesame"
    }
    % Class Onion
    Onion
    % Onion instproc taste {} {
      concat [$self next] "onion"
    }
    % Class Poppy
    Poppy
    % Poppy instproc taste {} {
      concat [$self next] "poppy"
    }
まだこれらは十分に機能しそうにはない。しかし,ここでの next の使い 方は,これらが自由に混ぜ合わされることを許している。
    % Class SesameOnionBagel -superclass {Sesame Onion SpreadableBagel}
    SesameOnionBagel
    % SesameOnionBagel abagel -spread butter
    % abagel taste
    raw! butter onion sesame
多重継承を行うために,システムはローカルなクラス包含関係を提供する ため,線形継承順序を判定する。この順序は info オプションによって調 べることができる。next は結合動作を行う場合に,この順序をだどる。
    % SesameOnionBagel info heritage
    Sesame Onion SpreadableBagel Bagel Object
我々の mixin を他のクラス,ベーグルとは全く関係のないクラスとも結合 させて,チップスのファミリーを作ることもできる。
    % Class Chips
    Chips
    % Chips instproc taste {} {
      return "crunchy"
    }
    % Class OnionChips -superclass {Onion Chips}
    OnionChips
    % OnionChips abag
    abag
    % abag taste
    crunchy onion

他の情報源

ベーグルに関して他にもいろいろなことができるが,もうリファレンスペー ジを読むべき段階だ。OTcl 言語は多くのタスクを行うのに必要な基本的な オブジェクト指向プログラミング機構を提供することを目的としている。 その一方で既存の機能あるいはあなた自身が作成した機能をを容易に拡張 可能にしている。

以下にチュートリアルで触れなかったいくつかの重要な事項を示す。


ishihara@cs.inf.shizuoka.ac.jp