2015-11-14-01-CL-STLにおけるファンクタの仕組み-2 - project-enigma

2015-11-14-01-CL-STLにおけるファンクタの仕組み-2

>> Site top >> weblog >> 月別アーカイブ >> 2015年11月のlog >> 2015-11-14-01-CL-STLにおけるファンクタの仕組み-2

最終更新日付:2015/11/14 11:06:59


CL-STLにおけるファンクタの仕組み-2

2015 年 11 月 14 日

前回の続き。CL-STL にあるいくつかのファンクタクラスを実際に見ていこうと思う。

まずはおさらい。処理を呼び出すためのマクロ functor-call は以下のようになっていた。

(defmacro functor-call (func &rest args)
  #+cl-stl-0x98
  (ecase (length args)
    (1 `(funcall (functor-function ,func) ,@args))
    (2 `(funcall (functor-function ,func) ,@args)))
  #-cl-stl-0x98
  `(funcall (functor-function ,func) ,@args))

 

ごちゃごちゃしているが、要するに functor-function をかけた結果を funcall しているだけだと思ってもらって良い。そして、総称関数 functor-function は以下のようになっている。

(defgeneric functor-function (func))
(defmethod  functor-function ((func cl:function))
  func)
(defmethod  functor-function ((func functor))
  (__functor-closure func))

 

普通の関数やラムダ式については与えられたパラメータをそのまま返し、functor の場合は __functor-closure をかけた結果を返す‥‥‥と。となると、問題は functor および __functor-closure の定義だな。以下のようになっている。

(defclass functor (clonable)
  ((closure :type     cl:function
            :initform nil
            :initarg  :closure
            :accessor __functor-closure)))

;; deprecated in 0x11 or later
(defclass unary-function  (functor) ())
(defclass binary-function (functor) ())

 

つまり、functor はクラスで、そのメンバである closure にアクセスするためのメソッドが __functor-closure というわけだ。この functor がファンクタの基底クラスとなる。unary-functionbinary-function は、古き良き C++98 時代のためのものだ。

そしてファンクタクラスに対する clone のサポートはデフォルトで以下のようになる。

(defmethod operator_clone ((func functor))
  (make-instance (type-of func)
                 :closure (__functor-closure func)))

 

これはあくまでデフォルトだ。他にも「コピー」しなければならないようなメンバを抱えているクラスは、独自に operator_clone を実装しなければならない。なんでも魔法のようにお手軽に、というわけにはいかないね。

それでは実際のファンクタクラスを見てみよう。まずは stl:plus からだ。

(defclass plus (#-cl-stl-0x98 functor
                #+cl-stl-0x98 binary-function) ())

(declare-constructor plus (0))

(labels ((__plus (arg1 arg2) (+ arg1 arg2)))
  (define-constructor plus ()
    (make-instance 'plus :closure #'__plus)))

 

なんというか、まぁ、これを見て呆れる方もいるだろう。わかってるってば。こんなもの誰も使いはしない。だって #'+ を渡す方がよっぼど手軽だし、多分速いからだ。では何故こんなモノが CL-STL には実装されているのか? 答えは簡単。STL にあるからだ。それ以上の理由はないし、自分でも正直実際に使ったことはない。

まだ読んでくれている方のために、気をとりなおして進めるとしよう。次の binder2nd はちょっと歯応えがあるはずだ。関数アダプタであるこいつは、バインドするファンクタ(または関数)と、2番目のパラメータに固定的に与えるパラメータの2つを保有する。コンストラクタや operator_clone の内部で何をやっているのか、興味のある方は良く見てほしい。

(defclass binder2nd (#-cl-stl-0x98 functor
                     #+cl-stl-0x98 unary-function)
  ((op  :initform nil
        :initarg  :operator
        :accessor binder2nd-operator)
   (arg :initform nil
        :initarg  :arg
        :accessor binder2nd-arg)))

(declare-constructor binder2nd (2))

(define-constructor binder2nd (op arg2)
  #+cl-stl-warn-deprecated
  (progn
    #-cl-stl-0x98 (warn "binder2nd is deprecated."))
  (let* ((op  (clone op))
         (fnc (functor-function op)))
    (make-instance 'binder2nd
                   :operator op
                   :arg      arg2
                   :closure (lambda (arg1)
                              (funcall fnc arg1 arg2)))))

(defmethod operator_clone ((func binder2nd))
  (let* ((op   (clone (binder2nd-operator func)))
         (arg2 (binder2nd-arg func))
         (fnc  (functor-function op)))
    (make-instance 'binder2nd
                   :operator op
                   :arg      arg2
                   :closure (lambda (arg1)
                              (funcall fnc arg1 arg2)))))

 

STL と同じように、binder2nd の作成を簡単にする bind2nd 関数ももちろんある。

(defun bind2nd (functor arg)
  #+cl-stl-warn-deprecated
  (progn
    #-cl-stl-0x98 (warn "bind2nd is deprecated."))
  (let* ((op  (clone functor))
         (fnc (functor-function op)))
    (make-instance 'binder2nd
                   :operator op
                   :arg      arg
                   :closure (lambda (arg1)
                              (funcall fnc arg1 arg)))))

 

折角だから、再び呆れてもらうために使うところをご覧頂くとしよう。こんな感じ。

(let ((fnc (new stl:binder2nd #'/ 3)))
  (stl:functor-call fnc 15))

;=> 5

 

うん、これならラムダ式で十分だよね。結局のところ、ほとんどのファンクタクラスやアダプタは、C++ がラムダ式をサポートしていなかった時代の産物なのだ。しかし Common Lisp には当たり前のようにラムダ式がある。だからファンクタクラス群を使うことはほとんどないのだ。

めげずに続けよう。一番苦労した bind マクロを‥‥‥と言いたいところなのだけれど、これは長過ぎるし説明しきれる気がしないので「cl-stl-functional.lisp を見てやってください」とだけ書いておこう。ひとまずのところ、ファンクタの仕組みについては前回の説明、および今回の binder2nd の例で大体はわかってもらえたのではないかと思う。それでは次回以降、再び Effective STL の話に戻るとしよう。

 

コメント

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

 

このページのタグ

Page tag : STLとその移植

Page tag : Common Lisp

 

 


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