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