2014-06-18-01-ファンクタの0x11対応 - 5 - project-enigma

2014-06-18-01-ファンクタの0x11対応 - 5

>> Site top >> weblog >> 月別アーカイブ >> 2014年06月のlog >> 2014-06-18-01-ファンクタの0x11対応 - 5

最終更新日付:2014/06/18 23:50:00


ファンクタの0x11対応 - 5

2014 年 06 月 18 日

ファンクタの C++11 対応の話、今回は、いよいよ bind の実装を見ていこう。というか、誰か助けてと言いたい今日この頃。

 

まずは bind マクロが作成する非公開のファンクタオブジェクトのクラス、__binded-expr の実装を以下に。

(defclass __binded-expr (functor)
  ((bindee  :initform nil
            :initarg  :bindee
            :accessor __binded-expr-bindee)
   (adapter :type     :function
            :initform nil
            :initarg  :adapter
            :accessor __binded-expr-adapter)))

(defmethod functor-function ((func __binded-expr))
  (lambda (&rest args)
    (apply (__binded-expr-adapter func)
           (__binded-expr-bindee  func) args)))

 

メンバ bindee はバインドされる関数、またはファンクタで、adapter__binded-expr に対する呼び出しを bindee に転送するための関数だ。前回の最後に、(stl:bind #'mult3 :1 2 3) が以下のように展開されるのを確認した。

(make-instance 'cl-stl::__binded-expr
               :bindee #'mult3
               :adapter
               (let ((#:prm2-36 2)
                     (#:prm3-37 3))
                 (lambda (#:fnc-34 #:arg1-35)
                   nil
                   (cl-stl:functor-invoke #:fnc-34
                                          #:arg1-35
                                          #:prm2-36
                                          #:prm3-37))))

 

実は、上記で adapter を初期化している lambda 式に渡されているパラメータ、#:fnc-34 に渡されることになるのが bindee なのだ。なぜ上記の lambda 式内部で素直に #'mult3 に対して funcall していないかというと、それがファンクタのコピーに関わる問題によるものなのだ。だから先のコードでは __binded-expr に対する clone の実装は提示しなかった‥‥‥だがその話は後だ。

さて、では bind の実装はどうなっているのか。こうなっている。

(defmacro bind (func &rest args)
  (let ((max-arg 0)
        (arg-hash (make-hash-table)))
    (labels ((get-argsym (idx)
               (let ((is-new nil)
                     (ret (gethash idx arg-hash)))
                 (unless ret
                   (setf ret (gensym (format nil "ARG~A-" idx)))
                   (setf (gethash idx arg-hash) ret)
                   (setf is-new t)
                   (when (< max-arg idx)
                     (setf max-arg idx)))
                 (values ret is-new)))
             (make-lambda-list (idx lambda-list ignore-list)
               (if (< max-arg idx)
                   (values (nreverse lambda-list)
                           (nreverse ignore-list))
                   (multiple-value-bind (sym is-new) (get-argsym idx)
                     (cl:push sym lambda-list)
                     (when is-new
                       (cl:push sym ignore-list))
                     (make-lambda-list (1+ idx) lambda-list ignore-list))))
             (imp (lst gensym-list param-list &optional (idx 1))
               (if (null lst)
                   (values (nreverse gensym-list)
                           (nreverse  param-list))
                   (let* ((item (car lst))
                          (ret  (is-placeholder item)))
                     (if (< 0 ret)
                         (cl:push (get-argsym ret) param-list)
                         (let ((sym (gensym (format nil "PRM~A-" idx))))
                           (cl:push sym param-list)
                           (pushnew `(,sym ,item) gensym-list)))
                     (imp (cdr lst) gensym-list param-list (1+ idx))))))
      (let ((param-list  nil)
            (gensym-list nil)
            (fnc-arg (gensym "FNC-")))
        (multiple-value-setq (gensym-list param-list) 
                             (imp args gensym-list param-list))
        (multiple-value-bind (lambda-list ignore-list)
                             (make-lambda-list 1 nil nil)
          `(make-instance '__binded-expr
                          :bindee ,func
                          :adapter
                          (let ,gensym-list
                            (lambda (,fnc-arg ,@lambda-list)
                              ,(when ignore-list
                                     `(declare (ignore ,@ignore-list)))
                              (stl:functor-invoke ,fnc-arg ,@param-list)))))))))

 

さぁ、自分としてはこれで精一杯というところだ。しかし、まだやるべきことはある。bindee に対する functor-function で返されるクロージャをキャッシュするべきだし、__binded-expr 自身に対する functor-function で作成しているクロージャも事前に作成してキャッシュしておくべきなのだ。だが、これ以上 bind マクロが複雑になることに耐えられるかどうか自信がない。いや、やってしまえばできることはわかっているのだ。しかし、まだ「コピーの問題」が解決できていない。どういうことかというと‥‥‥そうだな、まずは __binded-expr のための clone の実装を見てみよう。

(defmethod clone ((func __binded-expr))
  (make-instance '__binded-expr
                 :bindee  (clone (__binded-expr-bindee func))
                 :adapter (__binded-expr-adapter func)))

 

STL ではファンクタや反復子は全て『値渡し』、つまり、コピーで渡すようになっている。それに倣い、CL-STL でもコピーができるような仕組みとして、総称関数 clone を導入し、ファンクタや反復子にはその実装を義務付けている。だから __binded-expr もコピーができなければならない。それをやろうとした結果が上記の実装だが、たしかに bindee はコピーしている。これが先に見た (stl:bind #'mult3 :1 2 3) の展開結果において adapter が直接 #'mult3 をコールしていなかった理由だ。コピーされた __binded-expr では、adapter は共用されているが、そこに渡される bindee はコピーされたものになるからだ。

そして上記の段落における最後のポイント、adapter が共用されているというところに最後の問題がある。adapter は、バインドするパラメータの数だけレキシカルコンテキスト上の変数を参照している。先に見た (stl:bind #'mult3 :1 2 3) の例では、2 と 3 の2つだ。これはコピーしなくても良いのだろうか? STL では、「バインドされる変数もコピーされる」っぽいことがはっきりと書いてあるっぽい(表現がわやわやなのは英語力に自信がないからだ)。しかし、バインディングを実現するためにレキシカルクロージャに頼った結果、それをどうやってコピーすれば良いかわからないのだ。

‥‥‥ふぅ、結局、今回でもこの話題は終わらなかった。が、言いたいことはとりあえず書けたものと思う。こうやって書き出しただけでも、なんとなく自分が何をすれば良いかがわかってきたような気もするしね。かなり気合いの入った改造をすれば、ここまでに書いてきた問題はクリアできるような気がするのだ。よし、頑張ろう、自分。

 

コメント

このページにコメントする

 

このページのタグ

Page tag : Common Lisp

Page tag : STLとその移植

 

 


Copyright(C) 2005-2017 project-enigma.
Generated by CL-PREFAB.