最初に少し安心させることを言っておきましょう。
他の多くのオブジェクト指向言語処理系と同様に、Gaucheではすべてがオブジェクトです。
[Gaucheのクラス階層図(一部)]
Gaucheでは数値も文字列もすべて<top>クラスから派生したサブクラスに属します。
Gaucheの対話型インタプリタを起動して、
class-of手続きで確かめてみましょう。
gosh> (class-of 50) ;; 整数
#<class <integer>>
gosh> (class-of 3.14) ;; 実数
#<class <real>>
gosh> (class-of 1/2) ;; 有理数
#<class <rational>>
gosh> (class-of "たけやぶやけた") ;; 文字列
#<class <string>>
数値50は<integer>クラスのインスタンスであり、数値3.14は<real>クラスの、文字列"たけやぶやけた"は<string>クラスのインスタンスであることが分かります。
Gaucheでは全てのデータ型がオブジェクトシステムの上に構築されています。この観点から見れば「Gaucheはオブジェクト指向言語処理系である」と言えます。
かと言ってプログラマにオブジェクト指向的な考えを強制するものではなく、オブジェクト指向的な側面を意識せずプログラミングすることも可能です。この節の標題に「もしそれがお望みなら」と書いたのはこのことを意味しています。
Gaucheのオブジェクトシステムは、Common LispのオブジェクトシステムであるCLOS (Common Lisp Object System)の影響を受けています。CLOSからの影響にはクラスやメソッドや継承といった、他のオブジェクト指向言語処理系と共通した特徴もあります。しかしそればかりではなく他のオブジェクト指向言語処理系とは大きく異なった特徴も持っています。
他のオブジェクト指向言語処理系ユーザーが面食らうのは次の点でしょう。
クラスの"メンバー"になるのはスロットと呼ばれるデータメンバーだけです。メソッドはクラスとは別個に存在します。
例えば、2次座標を表すx座標とy座標を保持する<2d-point>クラスを定義してみましょう。
Gaucheでの<2d-point>クラスの定義は以下の通りです。
(define-class <2d-point> () (x y))
一般的にGaucheでのクラス定義は以下の構文をとります。
(define-class クラス名 (親クラス...)
(スロット仕様...))
Gaucheでのクラス名は他のデータや手続きと区別するために、通常は<記号と>記号で括って命名されます。これは単なる命名規約であり<>記号で括ることが強制されるわけではありません。
スロットとは何かを保存しておく場所です。スロットに相当するものは他のオブジェクト指向言語処理系ではメンバ変数とかフィールドなどと呼ばれています。ここではx座標とy座標を保存するx, yという2つのスロットを定義しています。
インスタンス生成にはmakeメソッドを使用します。
(make <2d-point>)
試しに<2d-point>クラスのインスタンスを生成し、a-pointという名前をつけてみます。
(define a-point (make <2d-point>))
a-pointには何の値も与えられていないので、x座標の値を10.0、y座標の値を20.0に変更してみましょう。スロットの値を変更するにはslot-set!手続きを使います。
(slot-set! a-point 'x 10.0)
(slot-set! a-point 'y 20.0)
一般的にslot-set!は次の構文をとります。
(slot-set! インスタンス名 スロット名 値)
スロット名には'が必要です。
スロットの値を得るにはslot-ref手続きが使えます。
slot-ref手続きは一般的に次の構文をとります。
(slot-ref インスタンス名 スロット名)
Gaucheの対話型インタプリタでslot-refを実行してみましょう。
gosh> (slot-ref a-point 'x)
10.0
gosh> (slot-ref a-point 'y)
20.0
スロットの値を得るにはrefメソッドも使えます。
gosh> (ref a-point 'x)
10.0
gosh> (ref a-point 'y)
20.0
<2d-point>型のインスタンスと移動量dx, dyの3つを引数にとるmove-by!メソッドを定義してみましょう。
(define-method move-by! ((p <2d-point>) dx dy)
(inc! (ref p 'x) dx)
(inc! (ref p 'y) dy))
move-by!メソッドはinc!手続きを使い、
<2d-point>インスタンスpのx, yスロットをそれぞれdx, dyだけ増加させます。
move-by!メソッドを使って、a-pointを移動してみましょう。
(move-by! a-point 5.0 2.0)
<2d-point>クラスと同様に、2次元ベクトルを表す<2d-vector>クラスを定義してみましょう。
(define-class <2d-vector> () (x y))
<2d-point>と<2d-vertor>2つの引数をとるmove-by!メソッドを定義してみます。
(define-method move-by! ((p <2d-point>) (v <2d-vector>))
(inc! (ref p 'x) (ref v 'x))
(inc! (ref p 'y) (ref v 'y)))
x成分1.0、y成分-4.0のb-vectorを定義し、move-by!メソッドで移動してみましょう。
(define b-vector (make <2d-vector>)) ;; <2d-vector>インスタンス生成
(slot-set! b-vector 'x 1.0) ;; x成分を1.0に変更
(slot-set! b-vector 'y -4.0) ;; y成分を-4.0に変更
(move-by! a-point b-vector) ;; a-pointをb-vectorの値だけ移動
ここで間違った呼出しを行ってみましょう。move-by!の引数を逆にして呼び出してみます。
(move-by! b-vector a-point)
これは以下のエラーになります。
*** ERROR: no applicable method for #<generic move-by! (1)> with arguments (#<<2d-vector> 0x816f330> #<<2d-point> 0x816a3c8>)
引数の順序を<2d-vector> <2d-point>の順にしても呼び出せるように、さらにmove-by!メソッドを定義してみましょう。
(define-method move-by! ((v <2d-vector>) (p <2d-point>))
(inc! (ref p 'x) (ref v 'x))
(inc! (ref p 'y) (ref v 'y)))
今度はmove-by!メソッドが正常に動作します。
(move-by! b-vector a-point)
他のオブジェクト指向言語処理系と異なり、move-by!メソッドは<2d-point>クラスや<2d-vector>クラスに属しているわけではありません。例えば他のオブジェクト指向言語処理系のメソッド呼出構文のように、
a-point.move-by!(5.0, 3.0)
といった構文になることはありません。
他のオブジェクト指向言語処理系では<2d-point>クラスに属するmove-by!を呼び出すのか、<2d-vector>クラスに属するmove-by!を呼び出すのかを選択する必要があります。
a-point.move-by!(b-vector)
b-vector.move-by!(a-point)
Gaucheでは単に次のように書けます。
(move-by! b-vector a-point)
さらにGaucheが他のオブジェクト指向言語処理系と大きく異なるのは次の2つの点です。
- メタオブジェクトプロトコル (MOP)
- クラス再定義機構
メタオブジェクトプロトコル(以下MOP)とは、オブジェクトシステムそのものをプログラマがカスタマイズできる仕組みです。多くのオブジェクト指向言語処理系では、プログラマは処理系が用意したオブジェクトシステムをそのまま使うだけであり、自由にカスタマイズすることはできないのとは対照的です。
MOPのおかげで実現できたのがクラス再定義機構です。これはクラス定義をやり直したとき、古いクラスから生成されたインスタンスを新しいクラスに合わせて変換できる仕組みです。
MOPやクラス再定義機構はプログラムの柔軟性や再利用性を飛躍的に高めてくれます。もし仕様変更があっても、クラス定義を変更するだけで古いインスタンスが引き続き使えるのです。必要があればMOPを使ってオブジェクトシステムの動作そのものを変更することも出来ます。
要約
- Gaucheではすべてがオブジェクトである
- Gaucheではオブジェクト指向的プログラミングもできるし、
オブジェクト指向的な考えを意識しないプログラミングも出来る
- Gaucheのオブジェクトシステムはクラスやメソッドや継承といった、
他のオブジェクト指向言語処理系と共通する特徴を持っている
- GaucheのオブジェクトシステムはMOPという、
他のオブジェクト指向言語処理系にあまりみられない特徴も持っている
- MOPはプログラムの柔軟性や再利用性を飛躍的に高める