lisp - 是否有一个与Python生成器相当的lisp等效项?

标签 lisp common-lisp generator

在Python中,您可以编写:

def firstn(n):
     num = 0
     while num < n:
         yield num
         num += 1

口齿不清是什么意思?

最佳答案

现有包装
使用GENERATORS下载、安装并加载Quicklisp系统。然后,使用package: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的文档。
成语
与构建函数不同,提供自定义值生成方式的数据源(如字符串中aregular 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-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))))))

宏的一个可能实现是将主体包装成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))))

绑定
广义上讲,使用高阶函数或直接使用NIL宏构建生成器会得到相同的结果您可以实现一个与另一个(个人而言,我更喜欢先定义宏,然后使用宏定义函数,但是做相反的事情也很有趣,因为您可以重新定义函数,而不必重新编译宏的所有用法)。
但是,仍然有一个区别:宏在迭代中重用相同的变量,而闭包每次都引入一个新的绑定。例如:
(let ((list))
  (dotimes (i 10) (push (lambda () i) list))
  (mapcar #'funcall list))

.... 返回:
(10 10 10 10 10 10 10 10 10 10)

普通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))

根据上述定义,我们可以看到:
(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))))

如果您愿意,可以从宏中引入内部dotimes,但这很少实现。

关于lisp - 是否有一个与Python生成器相当的lisp等效项?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44078331/

相关文章:

swing - Swing 操作向量的扩展

lisp - 作为函数参数传递时如何停止评估 lisp 形式?

recursion - 在Golang中为递归函数实现生成器( yield )的惯用方式

function - 替换子序列的标准函数

list - 检查列表中是否有一定数量的元素

lisp - LISP 中的快速排序

python - 如何编写迭代器?

python - Keras - 数据集的数据生成器太大而无法放入内存

functional-programming - Scheme/Lisp 嵌套循环和递归

clojure - 构建 LISP 机器需要多少原语?十、七还是五?