Scheme:MOP:パラメタライズドクラス

Scheme:MOP:パラメタライズドクラス



目的

「文字列のみを要素に持つリスト」とか、 「整数のみを要素に持つベクタ」とかいうクラスが欲しいことがたまにある。

強い型付き言語だとこういうクラスを多用するが、 Schemeではそのへんは運用で何とかしてしまう場合が多い。 しかし、規模が大きくなってきてデータの境界条件をきちんとマネージしたい 場合や、メタデータを用いていろいろやりたい場合には、型が「何でも有り」 だと困ることもある。

validator

Gaucheでは例えばvalidatorメタクラスを使ってスロットの値にvalidateを 噛ましてやるってことは出来なくはない。

  (use gauche.validator)
  (use srfi-1)

  (define-class <list-of-strings> ()
    ((elements :validator (lambda (o v)
                            (if (and (list? v)
                                     (every string? v))
                                v
                                (error "bad type of object" v)))
      ))
     :metaclass <validator-meta>)

STklosならvirtual slotとかactive slotで同様のことが出来るであろう。

しかし、「××のみを要素に持つ○○」の××と○○が変わる度に上のクラス定義を やるのはめんどくさい。C++ならテンプレートで一発だ。 もちろん、マクロを使ってたとえば

  (define-collection-of-xx-class <list-of-strings> (list? string?))

というフォームが上のクラス定義に展開されるようにすることは簡単だし 現実的でもあるんだが、なんかテンプレートによる解法に比べて裏技的というか、 あんまり綺麗な感じがしない。

そこで、敢えてMOPでやってみるの巻。

トライ

とりあえずざくっと。

 (use srfi-1)
 (use gauche.collection)
 (use gauche.mop.validator)

 (define-class <collection-of-meta> (<validator-meta> <class>)
   ((element-type :init-keyword :element-type :init-value <top>)
    (collection-type :init-keyword :collection-type :init-value <list>))
   )

 (define-method compute-slots ((class <collection-of-meta>))
   (let ((empty-collection (call-with-builder
                            (ref class 'collection-type)
                            (lambda (put! get) (get)))))
     (slot-push! class 'direct-slots
                 `(elements :validator
                            ,(lambda (o v)
                               (if (and (is-a? v (ref (class-of o) 'collection-type))
                                        (every (cute is-a? <> (ref (class-of o) 'element-type)) (coerce-to <list> v)))
                                   v
                                   (error "bad type of value" v)))
                            :init-value ,empty-collection))
     (next-method)))

これで、

 (define <list-of-strings> 
   (make <colleciton-of-meta> :element-type <string> :collection-type <list>))
 (define <vector-of-integer>
   (make <collection-of-meta> :element-type <integer> :collection-type <vector>))

みたいなことが出来る。これならクラスをパラメタライズしたメタクラスから スペシャライズしたクラスが作られているという実感がある。インスタンスは 確かにスペシャライズした型しか受け付けない。

  (define a (make <list-of-strings>))
  (ref a 'elements)  ==> '()
  (set! (ref a 'elements) 3) ==> error
  (set! (ref a 'elements) '("abc" "def")) ==> ok
  (set! (ref a 'elements) '("abc" 3))  ==> error

なお、これを書いているうちに、:validatorみたいにスロットアクセサを カスタマイズしたらslot-bound? がうまく動かなくなるというバグをみつけた。 上のコードでは初期値を設定してこれを回避している。

実用化

上のコードでは<list-of-string>のインスタンスの持つコレクションに アクセスする時にはいちいちelementsスロットにアクセスしなければならない。 <list-of-string>が<sequence>を継承するようにして、コレクションフレームワークと シーケンスフレームワークを定義するようにしてやれば、 <list-of-string> に対してmapをかけたり出来るようになる。 そのへんも自動化したいね。もう眠くなったのでまた今度。

builderとmodifierでチェックする

yamasushi(2013/05/14 22:37:52 UTC) validatorを使わずに、builderとmodifierでチェックしてみました。


Tags: gauche.mop.validator, MOP

More ...