2015-05-03-01-binderにするか、tupleにするか - project-enigma

2015-05-03-01-binderにするか、tupleにするか

>> Site top >> weblog >> 月別アーカイブ >> 2015年05月のlog >> 2015-05-03-01-binderにするか、tupleにするか

最終更新日付:2015/05/03 19:08:01


binderにするか、tupleにするか

2015 年 05 月 03 日

嗚呼、5月になってしまった。SVG ツールは作り続けているけれど、ここに書くようなことがなくて困っている。今回はちょっと脱線して、いくつかのデータをまとめるためだけのデータ構造をクロージャで作成するマクロについて。というか、そんなモノは既存でいくらでもあるのだろうけれど、まぁこういうの作るのが楽しいので。

具体的にどんなものかと言えば、以前書いていた、クロージャでクラスを作成するマクロから機能を削ぎ落としていった感じのものだ。パッケージ名を binder にするか tupleにするか迷っているが、ひとまず binder ということにして、その中でマクロ create が以下のように定義されている。

(defmacro create (&rest args)
  (unless (zerop (mod (length args) 2))
    (error "Odd numbers of parameter."))
  (labels ((make-itemlist (lst acc)
             (if (null lst)
                 (nreverse acc)
                 (let ((key (car lst)))
                   (unless (keywordp key)
                     (error "~A is not keyword." key))
                   (make-itemlist (cddr lst)
                                  (push `((,key) ,(gensym) ,(cadr lst)) acc)))))
           (make-clauses (fnc lst acc)
             (if (null lst)
                 (nreverse acc)
                 (make-clauses fnc (cdr lst)
                               (push (funcall fnc (car lst)) acc)))))
    (let ((g-key (gensym "KEY"))
          (g-new (gensym "NEW"))
          (g-set (gensym "SET"))
          (itms  (make-itemlist args nil)))
      `(let (,@(mapcar #'cdr itms))
         (lambda (,g-key &optional (,g-new ',g-set))
           (if (not (eq ,g-new ',g-set))
               (ecase ,g-key
                 ,@(make-clauses (lambda (e)
                                   `(,(first e) (setf ,(second e) ,g-new))) itms nil))
               (if (null ,g-key)
                   (values ,@(mapcar #'second itms))
                   (ecase ,g-key
                     ,@(make-clauses (lambda (e)
                                       `(,(first e) ,(second e))) itms nil)))))))))

 

このマクロは以下のように、キーワードパラメータを与えるような感じで任意の数の「キーと値の組み」を与える使い方をする。

(binder:create :name "random lisper" :age 40 :gender :male)

 

上記呼び出しの結果はクロージャに評価される。その使い方や内容を見る前に、上記のマクロがどう展開されるかを見よう。以下のように展開される。

(binder:create :name "random lisper" :age 40 :gender :male)

=> (let ((#:g1 "random lisper")
         (#:g2 40)
         (#:g3 :male))
     (lambda (#:key &optional (#:new '#:set))
       (if (not (eq #:new '#:set))
           (ecase #:key
             ((:name)   (setf #:g1 #:new))
             ((:age)    (setf #:g2 #:new))
             ((:gender) (setf #:g3 #:new)))
           (if (null #:key)
               (values #:g1 #:g2 #:g3)
               (ecase #:key
                 ((:name)   #:g1)
                 ((:age)    #:g2)
                 ((:gender) #:g3))))))

 

特に目新しいことは何もない。キーだけを指定して呼び出せば対応する値を返し、キーと一緒に新しい値を指定して呼び出せばその値をセットする。さらに、キーに nil を指定すれば全ての値を多値として返す。これらの作業を補助するための関数として、関数 get が用意されている。まぁ、これについてはわざわざ使い方を書くまでもないだろう。

(defun get (obj &optional (key nil))
  (funcall obj key))

(defun (setf get) (new-val obj key)
  (funcall obj key new-val))

 

さて、ひとまずはこんなところだ。これらのマクロは、CL-SVG を書いていて「複数のダイナミック変数の値をキャッシュしなければならない」局面が多く、そのコーディングが結構面倒だと思ったところからきている。そういうのにいちいち構造体を使うのは面倒なモノだ。キーワードを使ったタグだけで単一のクロージャにデータをまとめることができればそれが一番楽だろう。そういう目的には、冒頭で書いた「クロージャでクラスを作るマクロ」も向かない。構造体同様に事前の定義が必要だからだ。

とはいえ、まだこのマクロは CL-SVG では使用していない。パッケージ名を binder にするか tuple にするか決めていないし、もうひとつばかり補助マクロを作成しようかと思っているからだ。それについては、また次回にでも。

 

コメント

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

 

このページのタグ

Page tag : Common Lisp

 

 


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