2015-11-05-01-STL書籍の掲載コードをCL-STLで書いてみよう-17 - project-enigma

2015-11-05-01-STL書籍の掲載コードをCL-STLで書いてみよう-17

>> Site top >> weblog >> 月別アーカイブ >> 2015年11月のlog >> 2015-11-05-01-STL書籍の掲載コードをCL-STLで書いてみよう-17

最終更新日付:2015/11/05 12:33:39


STL書籍の掲載コードをCL-STLで書いてみよう-17

2015 年 11 月 05 日

今回は、愚直に構造体とクラスで作成した前回のコードを変更し、ラムダ式で十分に目的を達することができることを示そうと思う。点はコンスセル、ファンクタはラムダ式だ。

早速始めよう。ラムダ式をインラインで書くのであれば、以下のようになる。構造体定義も要らない。クラス定義も要らない。簡単なモノだ。

(defun new-point (x y) (cons x y))

(let ((lp (new stl:list #{(new-point 1 5)
                          (new-point 2 6)
                          (new-point 3 7)
                          (new-point 4 8)})))
  (stl:accumulate (stl:begin lp) (stl:end lp) (new-point 0 0)
                  (let ((num-points 0)
                        (x-sum 0)
                        (y-sum 0))
                    (lambda (avg-so-far p)
                      (declare (ignore avg-so-far))
                      (incf num-points)
                      (incf x-sum (car p))
                      (incf y-sum (cdr p))
                      (new-point (/ x-sum num-points) (/ y-sum num-points))))))

;=> (5/2 . 13/2)

 

ファンクタとして動作するラムダ式の作成を関数にするのであれば、以下のようになるだろう。複数の個所で同じことをしたいなら、こちらの方が良い。

(defun new-point (x y) (cons x y))

(defun point-average ()
  (let ((num-points 0)
        (x-sum 0)
        (y-sum 0))
    (lambda (avg-so-far p)
      (declare (ignore avg-so-far))
      (incf num-points)
      (incf x-sum (car p))
      (incf y-sum (cdr p))
      (new-point (/ x-sum num-points) (/ y-sum num-points)))))

(let ((lp (new stl:list #{(new-point 1 5)
                          (new-point 2 6)
                          (new-point 3 7)
                          (new-point 4 8)})))
  (stl:accumulate (stl:begin lp) (stl:end lp) (new-point 0 0) (point-average)))

;=> (5/2 . 13/2)

 

では次。accumulate ではなく for-each を使う場合だ。これは少しばかりトリッキーな(というほどでもないが)やり方をする必要がある。クロージャを2つ返すのだ。ファンクタとして動作するものと、結果を取得するためのものだ。

(defun new-point (x y) (cons x y))

(defun point-average ()
  (let ((num-points 0)
        (x-sum 0)
        (y-sum 0))
    (values (lambda (p)
              (incf num-points)
              (incf x-sum (car p))
              (incf y-sum (cdr p)))
            (lambda ()
              (new-point (/ x-sum num-points) (/ y-sum num-points))))))

(let ((lp (new stl:list #{(new-point 1 5)
                          (new-point 2 6)
                          (new-point 3 7)
                          (new-point 4 8)})))
  (multiple-value-bind (fnc result) (point-average)
    (stl:for-each (stl:begin lp) (stl:end lp) fnc)
    (funcall result)))

;=> (5/2 . 13/2)

 

もうひとつ、別のやり方を示しておこう。個人的には、こちらの方が好みだ。

(defun new-point (x y) (cons x y))

(defun point-average ()
  (let ((tag (gensym))
        (num-points 0)
        (x-sum 0)
        (y-sum 0))
    (lambda (&optional (p tag))
      (if (eq p tag)
          (new-point (/ x-sum num-points) (/ y-sum num-points))
          (progn
            (incf num-points)
            (incf x-sum (car p))
            (incf y-sum (cdr p)))))))

(let ((lp (new stl:list #{(new-point 1 5)
                          (new-point 2 6)
                          (new-point 3 7)
                          (new-point 4 8)})))
  (let ((result (stl:for-each (stl:begin lp) (stl:end lp) (point-average))))
    (funcall result)))

;=> (5/2 . 13/2)

 

改めて、クロージャはとても便利なモノだ。C++ でも 0x11 でクロージャが使えるようになったが、それまではファンクタクラスを作成するのが面倒臭くて STL に不自由を感じる局面は多かった。あと(良く知らないが)Java でも使えるんだっけ? クロージャはもう Lisp だけのものではなくなってしまったワケだけれど、そんなことは気にするに値しない。それよりも、プログラミングの世界でクロージャの利用が当たり前のことになっていったら良いなと思う。

‥‥‥以上、こんな感じで、CL-STL は関数やクロージャをそのままファンクタとして使用できるような工夫を(一応は)している。そのための仕組みは少々毛深くてエレガントさに欠けるのだけれど、ひとまず動作はするようになっている。次回以降は Effective STL のファンクタと関数の章に入ることになるが、その前に CL-STL におけるファンクタの仕組みについて書いてみるとしよう。次回ね。

 

このシリーズのこれまでのエントリ

 

コメント

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

 

このページのタグ

Page tag : STLとその移植

Page tag : Common Lisp

 

 


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