是否有一个简单的lisp相当于Python的生成器?

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)

但是请注意图书馆的作者说:

这个图书馆更像是一个有趣的玩具,尽管据我所知它确实有效.我不认为我曾经在应用程序代码中使用过这个,尽管我认为可以谨慎使用它.

也可以看看

  • ITERATE包提供了一种定义生成器以在其迭代工具中使用的方法.

  • SERIES包提供了类似流的数据结构和操作.

  • 库(同样的做法GENERATORS,据我所知).

关闭

实际上,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从宏中引入内部,但很少这样做.

  • 关闭是我想要的。谢谢! (2认同)