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 01:00:00
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 におけるファンクタの仕組みについて書いてみるとしよう。次回ね。
このシリーズのこれまでのエントリ
- 2015-09-19 - 第47項:書き込み専用コードの作成は避けよう
- 2015-09-20 - 第9項:消去オプションは注意して選択しよう
- 2015-09-22 - 第9項:消去オプションは注意して選択しよう - 2
- 2015-09-28 - 第9項:消去オプションは注意して選択しよう - 3
- 2015-09-30 - 第14項:reserve を使って不必要な割り当てを避けよう
- 2015-10-01 - 第17項:余分な容量を取り除くには「swap技法」を使おう
- 2015-10-09 - 第27項:コンテナのconst_iteratorをiteratorに変換するには、distanceとadvanceを使おう
- 2015-10-12 - 第5項:単一要素メンバ関数より範囲メンバ関数を使おう
- 2015-10-14 - 第30項:出力先範囲の大きさを確認しよう
- 2015-10-16 - 第31項:ソートの選択肢を知っておこう
- 2015-10-17 - 第31項:ソートの選択肢を知っておこう - 2
- 2015-10-23 - 第32項:本当に削除したい場合は、remove風アルゴリズムの後にeraseを使おう
- 2015-10-25 - 第33項:ポインタのコンテナには注意してremove風アルゴリズムを使おう
- 2015-10-28 - 第34項:ソート済み範囲を必要とするアルゴリズムに注意しよう
- 2015-10-30 - 第36項:copy_ifの正しい実装について理解しよう
- 2015-11-03 - 第37項:範囲に関する要約情報を取得するには、accumulateまたはfor_eachを使おう
- 2015-11-05 - 第37項:範囲に関する要約情報を取得するには、accumulateまたはfor_eachを使おう - 2
コメント
このページのタグ
Page tag : STLとその移植
Page tag : Common Lisp
Copyright(C) 2005-2021 project-enigma.
Generated by CL-PREFAB.