出于实际目的,使用现有库是明智的,但由于问题是关于如何实现惰性列表,我们将从头开始。
关闭
惰性迭代是指生成一个对象,该对象在每次被请求时都可以生成惰性序列的新值。
一个简单的方法是返回一个闭包,即一个关闭变量的函数,它在通过副作用更新其状态的同时产生值。
如果你评估:
(let ((a 0))
(lambda () (incf a)))
您获得了一个具有局部状态的函数对象,即这里名为a 的变量。
这是对该函数专有位置的词法绑定,如果您对同一表达式再次求值,您将获得一个不同的匿名函数,该函数具有自己的本地状态。
当你调用闭包时,a 中存储的值会递增并返回它的值。
让我们将这个闭包绑定到一个名为counter的变量,多次调用它并将连续的结果存储在一个列表中:
(let ((counter (let ((a 0))
(lambda () (incf a)))))
(list (funcall counter)
(funcall counter)
(funcall counter)
(funcall counter)))
结果列表是:
(1 2 3 4)
简单的迭代器
在您的情况下,您希望在编写时有一个从 5 开始计数的迭代器:
(inf 5)
这可以实现如下:
(defun inf (n)
(lambda ()
(shiftf n (1+ n))))
这里不需要添加let,参数到n的词法绑定是在调用函数时完成的。
随着时间的推移,我们将n 分配给正文中的不同值。
更准确地说,SHIFTF 将n 分配给(1+ n),但返回n 的先前值。
例如:
(let ((it (inf 5)))
(list (funcall it)
(funcall it)
(funcall it)
(funcall it)))
这给出了:
(5 6 7 8)
通用迭代器
标准dolist 需要一个正确的列表作为输入,您无法放置另一种数据并期望它工作(或者可能以特定于实现的方式)。
我们需要一个类似的宏来迭代任意迭代器中的所有值。
我们还需要指定迭代停止的时间。
这里有多种可能,让我们定义一个基本的迭代协议如下:
- 我们可以在任何对象上调用
make-iterator,连同任意参数,以获得一个迭代器
- 我们可以在迭代器上调用
next来获取下一个值。
- 更准确地说,如果有值,
next 返回该值,并将 T 作为辅助值;否则,next 返回 NIL。
让我们定义两个通用函数:
(defgeneric make-iterator (object &key)
(:documentation "create an iterator for OBJECT and arguments ARGS"))
(defgeneric next (iterator)
(:documentation "returns the next value and T as a secondary value, or NIL"))
使用泛型函数允许用户定义自定义迭代器,只要它们尊重上述指定的行为。
我们没有使用仅适用于急切序列的dolist,而是定义了自己的宏:for。
它隐藏了用户对make-iterator 和next 的调用。
换句话说,for 接受一个对象并对其进行迭代。
我们可以用(return v) 跳过迭代,因为for 是用loop 实现的。
(defmacro for ((value object &rest args) &body body)
(let ((it (gensym)) (exists (gensym)))
`(let ((,it (make-iterator ,object ,@args)))
(loop
(multiple-value-bind (,value ,exists) (next ,it)
(unless ,exists
(return))
,@body)))))
我们假设任何函数对象都可以充当迭代器,因此我们将next 专门用于function 类的值f,以便调用函数f:
(defmethod next ((f function))
"A closure is an interator"
(funcall f))
此外,我们还可以专门化 make-iterator 以使闭包成为自己的迭代器(我认为没有其他好的默认行为可以为闭包提供):
(defmethod make-iterator ((function function) &key)
function)
向量迭代器
例如,我们可以为向量构建一个迭代器,如下所示。我们将make-iterator 专门用于类vector 的值(这里命名为vec)。
返回的迭代器是一个闭包,所以我们可以在它上面调用next。
该方法接受默认为零的:start 参数:
(defmethod make-iterator ((vec vector) &key (start 0))
"Vector iterator"
(let ((index start))
(lambda ()
(when (array-in-bounds-p vec index)
(values (aref vec (shiftf index (1+ index))) t)))))
你现在可以写了:
(for (v "abcdefg" :start 2)
(print v))
这会打印以下字符:
#\c
#\d
#\e
#\f
#\g
列表迭代器
同样,我们可以构建一个列表迭代器。
这里为了演示其他类型的迭代器,让我们有一个自定义的光标类型。
(defstruct list-cursor head)
光标是一个对象,它保持对正在访问的列表中当前 cons-cell 的引用,即 NIL。
(defmethod make-iterator ((list list) &key)
"List iterator"
(make-list-cursor :head list))
我们定义next如下,专门针对list-cursor:
(defmethod next ((cursor list-cursor))
(when (list-cursor-head cursor)
(values (pop (list-cursor-head cursor)) t)))
范围
Common Lisp 还允许使用 EQL 专门器来专门化方法,这意味着我们提供给 for 的对象可能是特定的关键字,例如 :range。
(defmethod make-iterator ((_ (eql :range)) &key (from 0) (to :infinity) (by 1))
(check-type from number)
(check-type to (or number (eql :infinity)))
(check-type by number)
(let ((counter from))
(case to
(:infinity
(lambda () (values (incf counter by) t)))
(t
(lambda ()
(when (< counter to)
(values (incf counter by) T)))))))
make-iterator 的可能调用是:
(make-iterator :range :from 0 :to 10 :by 2)
这也返回一个闭包。
例如,在这里,您将迭代一个范围,如下所示:
(for (v :range :from 0 :to 10 :by 2)
(print v))
以上展开为:
(let ((#:g1463 (make-iterator :range :from 0 :to 10 :by 2)))
(loop
(multiple-value-bind (v #:g1464)
(next #:g1463)
(unless #:g1464 (return))
(print v))))
最后,如果我们对inf进行小修改(添加辅助值):
(defun inf (n)
(lambda ()
(values (shiftf n (1+ n)) T)))
我们可以这样写:
(for (v (inf 5))
(print v)
(when (= v 7)
(return)))
哪些打印:
5
6
7