出于实际目的,使用现有库是明智的,但由于问题是关于如何实现惰性列表,我们将从头开始。
闭包
惰性迭代是指生成一个对象,该对象在每次被请求时都可以生成惰性序列的新值。一个简单的方法是返回一个闭包,即一个关闭变量的函数,它在通过副作用更新其状态的同时产生值。
如果您评估:
(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))))
这里不需要添加 a 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"))
使用泛型函数允许用户定义自定义迭代器,只要它们尊重上述指定的行为。
我们没有使用仅适用于急切序列的 using 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)))))
我们假设任何函数对象都可以充当迭代器,因此我们专门针对classnext
的值,以便调用该函数:f
function
f
(defmethod next ((f function))
"A closure is an interator"
(funcall f))
此外,我们还可以专门make-iterator
使闭包成为自己的迭代器(我没有看到其他好的默认行为可以为闭包提供):
(defmethod make-iterator ((function function) &key)
function)
向量迭代器
例如,我们可以为向量构建一个迭代器,如下所示。我们专注make-iterator
于 class 的值(这里命名为vec
)vector
。返回的迭代器是一个闭包,所以我们可以调用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