2014-06-16-01-ファンクタの0x11対応 - 2
>> Site top >> weblog >> 月別アーカイブ >> 2014年06月のlog >> 2014-06-16-01-ファンクタの0x11対応 - 2
最終更新日付:2014/06/16 01:00:00
ファンクタの0x11対応 - 2
2014 年 06 月 16 日
「続きは明日」とか書いておきながら、数日放置してしまった。先日書いた通り、ぶつぶついいながらファンクタ機構の拡張をしている。今回は現状を説明できるところまで書く‥‥‥つもり。
経緯をすっとばして現状を書くと、ファンクタをコールするためのマクロ functor-invoke はこうなっている。
(defmacro functor-invoke (func &rest args) #-cl-stl-0x11 (ecase (length args) (1 `(funcall (functor-function ,func) ,@args)) (2 `(funcall (functor-function ,func) ,@args))) #+cl-stl-0x11 `(funcall (functor-function ,func) ,@args))
新しい仕組みでも、cl-stl-0x11 が *features* になければ以前と同じように振る舞う必要がある。だから上記のようになっているのだけれど、基本的には functor-function が返す関数をコールするだけのマクロだ。そして、functor-function は以下のようになっている。普通の関数や lambda 式がそのまま使えるようなメソッドも用意してある。
(defgeneric functor-function (func)) (defmethod functor-function ((func function)) func)
ついでながら、クラス unary-function および binary-function は deprecated となり、それらに対してさらなる基底クラスとして functor が追加された。
(defclass functor (cloneable) ()) (defclass unary-function (functor) ()) ; deprecated in 0x11 or later (defclass binary-function (functor) ()) ; deprecated in 0x11 or later
functor-function に話を戻そう。問題は、ファンクタクラスのオブジェクトはこの総称関数呼び出しに応じて関数(おそらくはレキシカルクロージャ)を返さなければならないということだ。まずは一番簡単な例として、CL-STL の世界では何の役にも立たない plus を見てみよう。
(defclass plus (#+cl-stl-0x11 functor #-cl-stl-0x11 binary-function) ()) (constructor/defun plus () (make-instance 'plus)) (defmethod functor-function ((func plus)) (lambda (arg1 arg2) (+ arg1 arg2)))
constructor/defun は今回の話題には関係しないので無視して頂くとして、plus に対する functor-function の実装が lambda 式を返すことが確認できればひとまずはよろしいかと。#'+ を返したって別に問題はないのだが、一応 binary-function なので、取りうるパラメータ数はきっかり2つにしておきたいわけだ。
ところで、実は上記の functor-function の実装、実は実態通りではない。実際には、以下のようになっている。
(labels ((__plus (arg1 arg2) (+ arg1 arg2))) (defmethod functor-function ((func plus)) #'__plus))
どういうことかというと、最初に書いた方法では、どうも遅いらしいということがわかったからだ。plus ならばクロージャを作成して返す際に保存すべきレキシカルコンテキストは存在しないので問題はない(かもしれない)が、変数を閉じ込めなければならないような類の他のファンクタの場合、事前にレキシカルクロージャを作成して保存しておき、functor-function 呼び出しに対しては毎回それを返却する、というのが速いらしかった。そんなわけで、レキシカルクロージャを返却する必要があるファンクタの実装は(基本的に)全てそうなっている。以下はパラメータを1つバインドすることで binary-function を unary-function に変換する binder1st の例だ。
(defclass binder1st (#+cl-stl-0x11 functor #-cl-stl-0x11 unary-function) ((op :initform nil :initarg :operator :accessor binder1st-operator) (arg :initform nil :initarg :arg :accessor binder1st-arg) (fnc :type :function :initform nil :initarg :closure :accessor binder1st-closure))) (labels ((__binder1st-ctor (op arg1) (let* ((op (clone op)) (fnc (functor-function op))) (make-instance 'binder1st :operator op :arg arg1 :closure (lambda (arg2) (funcall fnc arg1 arg2)))))) (constructor/defun binder1st (op arg1) (__binder1st-ctor op arg1)) (defmethod clone ((func binder1st)) (__binder1st-ctor (binder1st-operator func) (binder1st-arg func)))) (defmethod functor-function ((func binder1st)) (binder1st-closure func))
コンストラクタ、およびコピーのための clone メソッドが__binder1st-ctor を共用している。その中でレキシカルクロージャを作成してオブジェクトインスタンスに保存しておき、functor-function にて返却しているわけだ。
そう、そうだった。レストパラメータを使わずにファンクタの呼び出し規約を拡張する方法というのが今回説明したいことだった。まぁ結局のところ、不定数のパラメータを受け付けることが可能で、それを総称関数のインターフェース上にキレイに作り込む方法は見当たらなかった。だから、総称関数呼び出しをメソッド呼び出しそのものから分離する他ない。結果として、総称関数呼び出しは単純に関数(レキシカルクロージャ)を返却するだけの立場にまで降格させられた‥‥‥というわけなのだ。
しかしながら、それはまたもやパフォーマンス上の問題につながってしまった。続きはまた次回。
コメント
このページのタグ
Page tag : Common Lisp
Page tag : STLとその移植
Copyright(C) 2005-2021 project-enigma.
Generated by CL-PREFAB.