在Python中你可以这样写:
def firstn(n):
num = 0
while num < n:
yield num
num += 1
这相当于什么是lisp?
答案 0 :(得分:23)
使用GENERATORS
下载,安装和加载Quicklisp系统。然后,使用包:generators
(或者最好先定义自己的包)。
(ql:quickload :generators)
(use-package :generators)
为随机值定义无限生成器:
(defun dice (n)
(make-generator ()
;; repeatedly return a random value between 1 and N
(loop (yield (1+ (random n))))))
使用发电机:
(loop
with dice = (dice 6)
repeat 20
collect (next dice))
=> (1 2 6 1 1 4 4 2 4 3 6 2 1 5 6 5 1 5 1 2)
但是请注意图书馆的作者说:
这个图书馆更像是一个有趣的玩具,尽管据我所知 确实有效。我不认为我曾经在应用程序代码中使用过这个, 虽然我认为这很谨慎,但可能是。
ITERATE
包提供了一种定义generators以在其迭代工具中使用的方法。
SERIES
包提供类似流的数据结构和操作。
Snakes库(据我所知,与GENERATORS
的方法相同)。
在实践中,CL并不像Python那样依赖于生成器。相反,当人们需要懒惰序列时,他们依赖于闭包:
(defun dice (n)
(lambda ()
(1+ (random n))))
然后,等同于next
只是对dice
生成的thunk的调用:
(loop
with dice = (dice 6)
repeat 20
collect (funcall dice))
这是首选的方法,特别是因为不需要像生成器一样依赖分隔的延续。你的例子涉及一个状态,骰子示例不需要(有一个影响random
的隐藏状态,但这是另一个故事)。以下是您的计数器通常如何实施:
(defun first-n (n)
(let ((counter -1))
(lambda ()
(when (< counter n)
(incf counter)))))
或者,您可以设计一个接受回调函数的生成器,该函数由生成器为每个值调用。可以使用任何funcallable,这允许调用者保持对代码执行的控制:
(defun repeatedly-throw-dice (n callback)
(loop (funcall callback (1+ (random n)))))
然后,您可以按如下方式使用它:
(prog ((counter 0) stack)
(repeatedly-throw-dice 6
(lambda (value)
(if (<= (incf counter) 20)
(push value stack)
(return (nreverse stack))))))
请参阅PROG
的文档。
do-traversal
成语而不是构建函数,提供自定义生成值的方法(如字符串中regular expressions的匹配)的数据源也定期提供一个抽象其控制流的宏。您可以按如下方式使用它:
(block 'outer
(let ((counter 0) stack)
(do-repeatedly-throw-dice (value 6)
;; For each iteration of the infinite loop,
;; VALUE is bound to a new random value.
(if (<= (incf counter) 20)
(push value stack)
(return-from 'outer (nreverse stack))))))
与上述不同的是该块明确命名。这是因为DO-X
通常可以在其身体周围定义NIL
块,这意味着任何封闭的NIL
块都被遮蔽。
隐式NIL
块允许您轻松退出迭代:
(let ((counter 0) stack)
(do-repeatedly-throw-dice (value 6)
(if (<= (incf counter) 20)
(push value stack)
(return (nreverse stack))))))
宏的一个可能的实现是将主体包装成lambda形式并使用上面定义的基于回调的版本:
(defmacro do-repeatedly-throw-dice ((var n) &body body)
`(block nil (repeatedly-throw-dice ,n (lambda (,var) ,@body))))
直接扩展到循环也是可能的:
(defmacro do-repeatedly-throw-dice ((var n) &body body)
(let ((max (gensym)) (label (make-symbol "NEXT")))
`(prog ((,max ,n) ,var)
,label
(setf ,var (1+ (random ,max)))
(progn ,@body)
(go ,label))))
上述形式的宏观扩展的一步:
(BLOCK 'OUTER
(LET ((COUNTER 0) STACK)
(PROG ((#:G16053 6) VALUE)
#:NEXT (SETF VALUE (1+ (RANDOM #:G16053)))
(PROGN (IF (<= (INCF COUNTER) 20)
(PUSH VALUE STACK)
(RETURN-FROM 'OUTER (NREVERSE STACK))))
(GO #:NEXT))))
从广义上讲,构建具有高阶函数或直接使用do-
宏的生成器会产生相同的结果。你可以用另一个实现一个(个人而言,我更喜欢首先定义宏,然后使用宏来定义函数,但是反过来也很有趣,因为你可以重新定义函数而无需重新编译宏的所有用法。)
但是,仍然存在差异:宏在迭代中重用相同的变量,而闭包每次都引入了新的绑定。例如:
(let ((list))
(dotimes (i 10) (push (lambda () i) list))
(mapcar #'funcall list))
....返回:
(10 10 10 10 10 10 10 10 10 10)
Common Lisp中的大多数(如果不是全部)迭代器都倾向于这样工作,对于有经验的用户来说它不应该是一个惊喜(实际上相反的情况会令人惊讶)。如果通过重复调用闭包来实现dotimes
,结果会有所不同:
(defmacro my-dotimes ((var count-form &optional result-form) &body body)
`(block nil
(alexandria:map-iota (lambda (,var) ,@body) ,count-form)
,result-form))
根据上述定义,我们可以看到:
(let ((list))
(my-dotimes (i 10) (push (lambda () i) list))
(mapcar #'funcall list))
...返回:
(9 8 7 6 5 4 3 2 1 0)
为了与标准dotimes
具有相同的结果,您只需要在构建闭包之前评估迭代变量:
(let ((list))
(dotimes (i 10)
(let ((j i))
(push (lambda () j) list))))
如果您愿意,您可以随时从宏中引入内部let
,但很少这样做。