JavaScript
CoffeeScript
lisp

JS-CLOSで多重ディスパッチと多重継承

More than 5 years have passed since last update.

Common Lisp Object System(CLOS)に似たオブジェクトシステムをJavaScriptで使えるJS-CLOSの紹介です。
多重継承、多重ディスパッチ(総称関数)、インスタンス作成時の簡単な型チェックなどができます。

CLOSではオブジェクトは単なるスロットの集合で、メソッドはオブジェクトに属しません。メソッドに相当するのは総称関数です。
CLOSについての詳しい説明はwikipedia等を参照してください。

まずは小手調べ。MLやHaskellのパターンマッチみたいなことをしてみます。

fib.coffee
fib = define_generic()

define_method fib, [0], -> 0
define_method fib, [1], -> 1
define_method fib, ["number"], (n) ->
  fib(n - 1) + fib(n - 2)

fib 20
#=> 6765
  • define_generic() は総称関数を作ります。
  • define_method 総称関数, [パターン], メソッド は 総称関数 が適用される際に パターン に適合する実引数が与えられたら メソッド を呼ぶよう設定します。複数のパターンにマッチする時は、一番特殊なパターンが選択されます。

パターンは、

  • 具体値として === で比較されるオブジェクト
  • undefined --ワイルドカード
  • typeof で返される文字列 ("string", "function", など)
  • instanceofで比較されるクラス (コンストラクタ関数) のどれかです。

次に多重ディスパッチの例です。

bump.coffee
floor  = define_class []
carpet = define_class []
ball   = define_class []
glass  = define_class []

bump = define_generic()

define_method bump, [ball, floor],        -> 'bounce'
define_method bump, [glass, floor],       -> 'crash'
define_method bump, [undefined, carpet],  -> 'silence'

bump new ball, new floor                 #bounce
bump (make ball), (make floor)   #bounce

bump new glass, new floor                #crash

bump new ball, new carpet                #silence
bump new glass, new carpet               #silence
  • define_class [親], バリデータ はクラスを作ります。これはnew演算子、またはmake関数を適用するとインスタンスを生成する関数です。

最後に多重継承の例。

lion.coffee
# domain
animal = define_class [], (x) ->
  slot_exists x, 'voice', 'string'

cat = define_class [animal]     #猫は動物のスロットを継承

osx = define_class [], (x) -> 
  slot_exists x, 'hostname', 'string'

lion = define_class [cat, osx]       #ライオンは猫とOSX両方のスロットを継承

# definitions

say = define_generic()
define_method say, [animal], (a) -> a.voice 
define_method say, [cat], (c) -> c.voice + ' meow'

hostname = define_generic()
define_method hostname, [osx], (o) -> o.hostname

# main

alice = make lion,
             voice: 'roar'
             hostname: 'example.com'

say alice
#=> 'roar meow'

hostname alice
#=> 'example.com'
  • slot_exists オブジェクト, キー, パターン は、*オブジェクト に キー が存在し、かつ パターン にマッチする値が格納されている場合にtrueを返します。
  • define_classに第2引数として与えられた関数は、インスタンス生成時のスロット初期化の際に呼ばれます。falseを返すと初期化を失敗させることができます。

菱形継承問題などのコーナーケースはだいたい考慮されていません。

よかったら遊んでみてください。プルリクエストお待ちしています。