如何在Scheme(Racket或ChezScheme)中实现Python风格的生成器?

如何在Scheme(Racket或ChezScheme)中实现Python风格的生成器?,scheme,Scheme,今天,我使用Scheme解决了N-queen问题,但与同一版本的Python相比,它的速度非常慢。当N=8时,方案需要90多秒!我知道一个原因是我不能在Scheme中使用生成器,我的代码必须首先形成大的列表,这对内存和计算来说是一场噩梦 关于Scheme中生成器的主题很少,这是我发现的唯一一个可能有用的主题,但遗憾的是,它在racket或chez Scheme中都不起作用 实际上,我只想要一个简单版本的python生成器,也就是说,不要形成整个列表,只需一次向我发送一个值。i、 e: (rang

今天,我使用Scheme解决了N-queen问题,但与同一版本的Python相比,它的速度非常慢。当N=8时,方案需要90多秒!我知道一个原因是我不能在Scheme中使用生成器,我的代码必须首先形成大的列表,这对内存和计算来说是一场噩梦

关于Scheme中生成器的主题很少,这是我发现的唯一一个可能有用的主题,但遗憾的是,它在racket或chez Scheme中都不起作用

实际上,我只想要一个简单版本的python生成器,也就是说,不要形成整个列表,只需一次向我发送一个值。i、 e:

(range 100000) ; will consume a large memory

(define g (generator 100000)) ; will do nothing
(next g) ;0 <-you call it with next one time, it returns one value
(next g) ;1
;...
(next g) ;100000
(next g) ;return a value that indicates the end, such as #f.

在这种情况下使用continuations(如链接中所建议的)是不合理的。这里有一个更简单的想法:让我们将生成器定义为thunk(一个无参数函数),它将起点、允许的最大值、增量和当前元素存储为其环境的一部分。每次调用过程时,当前元素都会更新。以下代码的行为类似于Python 3.x函数(或Python 2.x):

现在,
下一步
过程将简单地调用生成器,直到达到最大值,此时生成器将开始为每个后续调用返回
#f

(define (next generator)
  (generator))
例如:

(define g (generator 0 3 1))
(next g) ; 0
(next g) ; 1
(next g) ; 2
(next g) ; 3
(next g) ; #f
另一种选择是使用streams,但我将坚持使用上面的解决方案,它足够简单,应该适用于任何Scheme解释器。还有另一种选择——如果您在Racket中编码,只需使用a(也是一个流),如下所示:

(for ([i (in-range 0 4 1)])
  (display i))

=> 0123
我发现它的执行速度明显快于遍历列表:

(do ((i 0 (add1 i)))
  ((= i 100000) 'result)
   (some-function! i some-data))
如果您想更具功能性,Racket文档建议您与
for
及其变体一起使用

(for/list ((i (in-list (range 0 100000))))
  (some-function i some-data))

我有一个使用guile提示实现spidermonkey生成器的make迭代器过程(与ECMAScript 6生成器类似但不同)。由于racket也有提示,这应该可以直接转换为racket的调用,并使用continuation提示符和abort current continuation代替guile的调用,并使用prompt和abort to提示符

代码如下:

;; this procedure takes a generator procedure, namely a procedure
;; which has a 'yield' parameter for its first or only argument,
;; followed by such other arguments (other than the one for the
;; 'yield' parameter) as the generator procedure requires, and
;; constructs an iterator from them.  When the iterator is invoked, it
;; will begin executing the procedure unless and until the argument
;; comprising the yield procedure is called, which will cause the
;; iterator to suspend computation and instead return the value passed
;; to yield (yield is a procedure taking one argument).  If invoked
;; again, the iterator will resume computation at the point where it
;; last left off (returning a list of the values, if any, passed to
;; the iterator on resuming).  When the generator procedure has
;; executed to the end, the iterator returns 'stop-iteration.  This
;; procedure is intentionally modelled on javascript/spider-monkey
;; generators.  It has some resemblance to call/ec, except that (i)
;; instead of executing the passed procedure immediately, it returns
;; an iterator which will do so, (ii) it is resumable, and (iii) the
;; procedure to be executed can receive starting arguments in addition
;; to the yield/break argument, to provide an alternative to binding
;; them with a lambda closure.
(define (make-iterator proc . args)
  (define tag (make-prompt-tag))
  (define send-back '())
  (define (thunk)
    (apply proc
           (lambda (val)
             (abort-to-prompt tag val)
             send-back)
           args)
    ;; the generator procedure has returned - reset thunk to do
    ;; nothing except return 'stop-iteration and return
    ;; 'stop-iteration after this last call to proc
    (set! thunk (lambda () 'stop-iteration))
    'stop-iteration)
  (lambda send-args
    (set! send-back send-args)
    (call-with-prompt tag
                      thunk
                      (lambda (cont ret)
                        (set! thunk cont)
                        ret))))
以下是管道衬砌的程序:

;; for-iter iterates until the iterator passed to it (as constructed
;; by make-iterator) returns 'stop-iteration.  It invokes the procedure
;; passed as a second argument with the value yielded by the iterator
;; on each iteration.  It is mainly used for composing lazy operations
;; by pipelining, as for example with lazy-map and lazy-filter.
(define (for-iter iter proc)
  (let loop()
    (let ([val (iter)])
      (if (not (eq? val 'stop-iteration))
          (begin
            (proc val)
            (loop))))))

;; lazy-map is a procedure which takes an input iterator constructed
;; by make-iterator and a standard procedure, and then returns another
;; iterator (the output iterator) which yields the values obtained by
;; applying the standard procedure to the input iterator's yielded
;; values.
(define (lazy-map iter proc)
  (make-iterator (lambda (yield)
                   (for-iter iter (lambda (val) (yield (proc val)))))))

;; lazy-filter is a procedure which takes an input iterator
;; constructed by make-iterator, and then returns another iterator
;; (the output iterator) which yields such of the values yielded by
;; the input iterator as are those for which the predicate proc
;; returns #t
(define (lazy-filter iter proc)
  (make-iterator (lambda (yield)
                   (for-iter iter (lambda (val) (if (proc val) (yield val)))))))
这是Rhino第六版第280页的典型反例:

(define (counter yield initial)
  (let loop ([next-value initial])
    (let ([increment (yield next-value)])
      (if (not (null? increment))
          (if (eq? (car increment) 'reset)
              (loop initial)
              (loop (+ next-value (car increment))))
          (loop (+ 1 next-value))))))

(define counter-iter (make-iterator counter 10))   ;; create iterator at 10
(display (counter-iter))(newline)                  ;; prints 10
(display (counter-iter 2))(newline)                ;; prints 12
(display (counter-iter 'reset))(newline)           ;; prints 10
我还有一个作为宏的回指版本,它将一个yield键名注入到代码体中,但我更喜欢上面的方法

编辑:

对于不支持提示的方案实现,以下操作与使用提示的版本相同。但是,使用guile时,提示比使用完整调用/抄送延续更有效(我想这并不一定适用于所有实现):


经典序列可以用几行在ChezScheme中实现。以下是我的版本:

(library (seq)
  (export seq hd tl range smap force-seq for)
  (import (scheme))

  (define-syntax seq
    (syntax-rules ()
      ((_ a b) (cons a (delay b)))))

  (define hd car)
  (define (tl s) (force (cdr s)))

  (define (range-impl a b s)
    (cond ((< a b) (seq a (range-impl (+ a s) b s)))
          (else    '())))


  (define (range a . b)
    (cond ((null? b)       (range-impl 0 a 1))
          ((null? (cdr b)) (range-impl a (car b) 1))
          (else            (range-impl a (car b) (cadr b)))))

  (define (smap f s)
    (cond ((null? s) '())
          (else      (seq (f (hd s)) (smap f (tl s))))))

  (define (force-seq s)
    (when (not (null? s))
      (force-seq (tl s))))

  (define-syntax for
    (syntax-rules ()
      ((_ v r body ...) (force-seq (smap (lambda (v) body ...) r)))))
)
使用序列,可以轻松地以python方式从文件中读取行:

(library (io)
  (export getline lines)
  (import (scheme))
  (import (seq))

  (define (getline ip)
    (define (copy-line)
      (let ((c (get-char ip)))
        (unless (or (eof-object? c)
                    (eqv? c '#\newline))
          (display c)
          (copy-line))))
    (let ((c (peek-char ip)))
      (cond ((eof-object? c) #f)
            (else (with-output-to-string copy-line)))))


    (define (lines ip)
      (let ((l (getline ip)))
        (cond (l    (seq l (lines ip)))
              (else '()))))
)
然后你可以写:

(import (seq))
(import (io))

(for l (lines (current-input-port))
  (display l)
  (newline))

您可以在中找到解决方案。它不是完全重复的,但它展示了如何实现在连续调用中返回连续值的函数。@Joshua Taylor,谢谢。我会研究的。当我更详细地阅读你的问题时,我认为scar提供的简单解决方案可能足以满足你的特殊需要。我提供的链接更多地涉及标题“如何在Scheme中实现Python样式生成器”中的一般问题。对于一个简单的范围生成器,这很容易,因为生成过程的状态非常简单。对于更复杂的事情,最好有更充分的延续能力。生成器相当于惰性列表。在Scheme中定义非记忆的、SICP样式的惰性列表非常容易,而且它们对于许多问题都是足够的——我猜对于queens问题也是如此。见下半部分。
(library (seq)
  (export seq hd tl range smap force-seq for)
  (import (scheme))

  (define-syntax seq
    (syntax-rules ()
      ((_ a b) (cons a (delay b)))))

  (define hd car)
  (define (tl s) (force (cdr s)))

  (define (range-impl a b s)
    (cond ((< a b) (seq a (range-impl (+ a s) b s)))
          (else    '())))


  (define (range a . b)
    (cond ((null? b)       (range-impl 0 a 1))
          ((null? (cdr b)) (range-impl a (car b) 1))
          (else            (range-impl a (car b) (cadr b)))))

  (define (smap f s)
    (cond ((null? s) '())
          (else      (seq (f (hd s)) (smap f (tl s))))))

  (define (force-seq s)
    (when (not (null? s))
      (force-seq (tl s))))

  (define-syntax for
    (syntax-rules ()
      ((_ v r body ...) (force-seq (smap (lambda (v) body ...) r)))))
)
(import (seq))
(for x (range 5 12)
  (display x)
  (newline))
(library (io)
  (export getline lines)
  (import (scheme))
  (import (seq))

  (define (getline ip)
    (define (copy-line)
      (let ((c (get-char ip)))
        (unless (or (eof-object? c)
                    (eqv? c '#\newline))
          (display c)
          (copy-line))))
    (let ((c (peek-char ip)))
      (cond ((eof-object? c) #f)
            (else (with-output-to-string copy-line)))))


    (define (lines ip)
      (let ((l (getline ip)))
        (cond (l    (seq l (lines ip)))
              (else '()))))
)
(import (seq))
(import (io))

(for l (lines (current-input-port))
  (display l)
  (newline))