#:g1: SBCL: Iterator Protocolの紹介

Posted 2014-12-07 15:00:00 GMT

(LISP Library 365参加エントリ)

 LISP Library 365 の342日目です。

SBCL: Iterator Protocolとはなにか

 SBCL: Iterator Protocolは、SBCLの拡張で、ユーザー定義のsequenceを扱える繰り返し規約です。

パッケージ情報

パッケージ名SBCL: Iterator Protocol
ドキュメントSBCL User Manual: Iterator Protocol

インストール方法

 SBCLの標準機能で、sb-sequence(sequence)パッケージで定義されています。

試してみる

 341日目で紹介した、SBCL: Extensible Sequencesに近いところですが、ユーザー定義のsequenceを扱う繰り返し規約を定義できる仕組みです。
繰り返し規約というと、DylanのIteration Protocolを思い出しますが、Dylanのものにかなり影響を受けています。
Dylanでは、forward-iteration-protocolと、backward-iteration-protocolのメソッドがそれぞれ8つの値を返しますが、SBCLの場合は、一つに統合されていて、方向を示す値が1つ追加されて9つの値の値を返します。

 では、適当にリストでも定義してみます。

(defclass kons (sequence standard-object)
  ((kar :accessor kar :initarg :kar)
   (kdr :accessor kdr :initarg :kdr))) 

(defun kons (x y) (make-instance 'kons :kar x :kdr y))

(defun lyst (&rest xs) (loop :for x :in (reverse xs) :for tail := (kons x nil) :then (kons x tail) :finally (return tail)))

(defmethod sequence:iterator-endp ((seq kons) iterator limit from-end) (eq iterator limit))

(defmethod sequence:iterator-step ((s kons) iterator from-end) (if from-end (if (eq iterator s) SB-IMPL::*EXHAUSTED* (do* ((xs s (kdr xs))) ((eq (kdr xs) iterator) xs))) (kdr iterator)))

(defmethod sequence:iterator-element ((s kons) iterator) (kar iterator))

(defmethod (setf sequence:iterator-element) (o (s kons) iterator) (setf (kar iterator) o))

(defmethod sequence:iterator-index ((s kons) iterator) ;; FIXME: this sucks. (In my defence, it is the equivalent of the ;; Apple implementation in Dylan...) (do ((tail s (kdr tail)) (i 0 (1+ i))) ((null tail)) (when (eq tail iterator) (return i))))

(defmethod sequence:iterator-copy ((s kons) iterator) iterator)

(defmethod sequence:length ((s kons)) (do ((tail s (kdr tail)) (i 0 (1+ i))) ((null tail) i)))

(defun nthkdr (n kons) (do ((tail kons (kdr tail)) (i 0 (1+ i))) ((or (null tail) (= n i)) tail)))

(defun kons-last (kons &optional (n 1)) (do ((tail kons (kdr tail))) ((null (nthkdr n tail)) tail)))

(defmethod sequence:make-simple-sequence-iterator ((s kons) &key from-end (start 0) end) (if from-end (let* ((termination (if (= start 0) sb-impl::*exhausted* (nthkdr (1- start) s))) (init (if (<= (or end (length s)) start) termination (if end (kons-last s (- (length s) (1- end))) (kons-last s))))) (values init termination t)) (cond ((not end) (values (nthkdr start s) nil nil)) (t (let ((st (nthkdr start s))) (values st (nthkdr (- end start) st) nil))))))

書いてみたというより殆どSBCLのlistを扱う箇所のパクリですが、これで、sequence:dosequenceが使えるようになります。

(sequence:dosequence (e (lyst 0 1 2 3))
  (print e))
;>>  
;>>  0 
;>>  1 
;>>  2 
;>>  3 
;=>  NIL

 mapも内部ではdosequenceを利用しているので同じく扱えるように(新しいユーザー定義のsequenceを返すには別途定義が別途必要)

(map nil #'print (lyst 0 1 2 3))
;>>  
;>>  0 
;>>  1 
;>>  2 
;>>  3 
;=>  NIL

 更に、loopにもsequenceを扱うloop-pathが定義されているので、こんな感じに書けるようになります。

(loop :for e :being :the :elements :in (lyst 0 1 2 3)
      :collect e)
;=>  (0 1 2 3)

(loop :for e :being :each :element :of (lyst 0 1 2 3) :collect e) ;=> (0 1 2 3)

まとめ

 今回は、SBCL: Iterator Protocolを紹介してみました。
sequenceを扱うためのloop-pathはおまけ的に定義されている気もしますが、ひょっとするとこれが一番有用かもしれません。

comments powered by Disqus