オブジェクトの作り方

メソッド(メンバ関数)とプロパティ(メンバ変数)を1つずつ持つオブジェクトを例に、クラス風オブジェクト作成や継承について解説します。

obj: Object.clone
obj.var:: 0
obj.func: `x[
  self.var.* x
]

//以下テスト
obj.var.= 10
obj.func(2).flush //20が出力

このオブジェクトをテンプレートとして、同じようなオブジェクトを複数生成してみます。

obj: Object.clone
obj.var:: 0
obj.func: `x[
  self.var.* x
]

//以下テスト
ins1: obj.clone
ins2: obj.clone
ins1.var.= 10
ins2.var.= 20
"%1% / %2%".format(ins1.func(2) ins2.func(2)).flush //"20 / 40"ではなく"40 / 40"が出力される

varが共用されているため、期待される結果を得られません。varをオブジェクトごとに独立させる必要があります。clone時に自動的に呼ばれるOnCloneを使います。

obj: Object.clone
obj.var:: 0
obj.func: `x[
  self.var.* x
]
obj.OnClone: `[
  self.var:: self.parent.var
]

//以下テスト
ins1: obj.clone
ins2: obj.clone
ins1.var.= 10
ins2.var.= 20
"%1% / %2%".format(ins1.func(2) ins2.func(2)).flush //"20 / 40"が出力される

これでvarはcloneで生成された新しいオブジェクトごとに保持されるようになりました。しかしオブジェクトを生成するのはcloneだけではありません。cloneのほかにもうひとつだけ、オブジェクトを生成するものがあります。copyです。

obj: Object.clone
obj.var:: 0
obj.func: `x[
  self.var.* x
]
obj.OnClone: `[
  self.var:: self.parent.var
]

//以下テスト
ins1: obj.clone
ins2: ins1.copy
ins1.var.= 10
ins2.var.= 20
"%1% / %2%".format(ins1.func(2) ins2.func(2)).flush //"20 / 40"ではなく"40 / 40"が出力される

copyされたオブジェクトは同じスロットに同じ参照を保持するためこうなります。cloneと同じように、OnCopyを定義することで対応します。

obj: Object.clone
obj.var:: 0
obj.func: `x[
  self.var.* x
]
obj.OnClone: `[
  self.var:: self.parent.var
]
obj.OnCopy: `[
  self.var$$
]

//以下テスト
ins1: obj.clone
ins2: ins1.copy
ins1.var.= 10
ins2.var.= 20
"%1% / %2%".format(ins1.func(2) ins2.func(2)).flush //"20 / 40"が出力される

これでやっと、普通のオブジェクト指向言語のクラスのようになりました。次は普通のオブジェクト指向言語のように継承をしてみます。

obj: Object.clone
obj.var:: 0
obj.func: `x[
  self.var.* x
]
obj.OnClone: `[
  self.var:: self.parent.var
]
obj.OnCopy: `[
  self.var$$
]

derived: obj.clone
derived.derived_var:: 0
derived.derived_func: `x[
  self.func(x).* (self.derived_var)
]
derived.OnClone: `[
  resend
  self.derived_var:: self.parent.derived_var
]
derived.OnCopy: `[
  resend
  self.derived_var$$
]

//以下テスト
ins1: derived.clone
ins2: ins1.copy
ins1.var.= 10
ins2.var.= 20
ins1.derived_var.= 30
ins2.derived_var.= 40
"%1% / %2%".format(ins1.derived_func(2) ins2.derived_func(2)).flush //"600 / 1600"が出力される

派生オブジェクトのOnCloneとOnCopyから、親のそれらを呼び出すためresendしています。見て分かるとおり、OnCloneとOnCopyが非常に冗長です。同じようなものは纏めるべきです。OnCloneとOnCopyを請け負うオブジェクトを継承することで解決してみます。

class: Object.clone
class.OnClone: `[
  self.parent.slot.each& `e[
    e.second.type?(:Method).! [
      self.def_copy(e.first e.second)
    ]
  ]
]
class.OnCopy: `[
  self.slot.each& `e[
    e.second.type?(:Method).! [
      self.get_copy(e.first)
    ]
  ]
]

obj: class.clone
obj.var:: 0
obj.func: `x[
  self.var.* x
]

derived: obj.clone
derived.derived_var:: 0
derived.derived_func: `x[
  self.func(x).* (self.derived_var)
]

//以下テスト
ins1: derived.clone
ins2: ins1.copy
ins1.var.= 10
ins2.var.= 20
ins1.derived_var.= 30
ins2.derived_var.= 40

"%1% / %2%".format(ins1.derived_func(2) ins2.derived_func(2)).flush //"600 / 1600"が出力される

新たにオブジェクトを生成するオブジェクトのスロットにあるメソッド以外のオブジェクトを、新たに生成されるオブジェクトにコピーするというのがこのclassオブジェクトの役割です。強引なやり方ですが、生成されるオブジェクトに差異はありません。

まとめ

クラス風オブジェクトの作成というのを想定していない(そのための機能が提供されていない)言語でありながら、プロトタイプベースの柔軟性により、意外と簡単にいろいろ出来るようです。
classオブジェクトをもう少し洗練したようなものを組み込みオブジェクトとしておいたほうがいいような気もしますが、なにか違うような気もするので、とりあえずはこのままです。