概要
Common Lispでiteratorもどきをつくってみました。ちなみに私はiteratorとは何かをあまりわかってないですが、それにも関わらず書いてます。分かったらそのうち書き直すと思います。
追記 (2015/02/01): LISP界隈(?)ではgeneratorと言うらしいです。
ユースケースは、mapcar
にすると重く、さらに途中で中断するかもしれないからリスト全体をmapcar
するのは無駄という割とニッチな場面なんだと思います。
コード
;; iteratorはhas-nextとgetを持った構造体
(defstruct iter has-next get)
(defmacro each-iter ((var iter) &body body)
`(loop while (funcall (iter-has-next ,iter)) do
(let ((,var (funcall (iter-get ,iter)))) ,@body)))
;; 例
(defun primep (num)
(loop for i from 2 to (sqrt num)
when (= (rem num i) 0) do (return-from primep nil))
t)
(defun get-next-prime (start)
(loop for i from start
when (primep i) do (return i)))
(defun make-prime-iter (start end)
(let ((next-prime (1- start)))
(make-iter
:has-next
(lambda ()
(setq next-prime (get-next-prime (1+ next-prime)))
(<= next-prime end))
:get
(lambda ()
next-prime))))
(let ((count 0)
(iter (make-prime-iter 1000 2000)))
(each-iter (prime iter)
(print prime)
(incf count)
(when (<= 10 count)
(return))))
1000から2000までの素数を一度に列挙してリスト処理するのはさすがに重いので逐次処理になってます。最初の10個でいいや、みたいな時でもreturnできます。
見ての通りクロージャの威力が発揮されてます。
最後に
マクロで包まれているとはいえ、明示的にfuncall
するのはなんとかしたいですね。defstruct
を工夫すれば大丈夫だと思います。
これってある種の遅延評価なんでしょうかね?