Implement yield and send in Scheme

前端 未结 3 1766
你的背包
你的背包 2020-12-06 03:17

I\'m trying to port yield and yield from from Python to Scheme.

Here is an implementation I\'ve done:

(define (coroutine ro         


        
相关标签:
3条回答
  • 2020-12-06 03:57

    Something like this:

    (define (make-generator procedure)
      (define last-return values)
      (define last-value #f)
      (define (last-continuation _) 
        (let ((result (procedure yield))) 
          (last-return result)))
    
      (define (yield value)
        (call/cc (lambda (continuation)
                   (set! last-continuation continuation)
                   (set! last-value value)
                   (last-return value))))
    
      (lambda args
        (call/cc (lambda (return)
                   (set! last-return return)
                   (if (null? args)
                       (last-continuation last-value)
                       (apply last-continuation args))))))
    

    Used like this:

    (define test 
     (make-generator
       (lambda (collect)
         (collect 1)
         (collect 5)
         (collect 10)
         #f)))
    
    (test) ; ==> 1
    (test) ; ==> 5
    (test) ; ==> 10
    (test) ; ==> #f (procedure finished)
    

    Now we can wrap the internals into a macro:

    (define-syntax (define-coroutine stx)
      (syntax-case stx ()
        ((_ (name . args) . body )
         #`(define (name . args)
             (make-generator 
              (lambda (#,(datum->syntax stx 'yield))
                . body))))))
    

    Notice that define-coroutine is implemented using syntax-case since we need to make yield unhygienic.

    (define-coroutine (countdown-from n)
      (let loop ((n n))
        (if (= n 0)
            0
            (loop (- (yield n) 1)))))
    
    (define countdown-from-10 (countdown-from 10))
    
    (define (ignore procedure)
      (lambda ignore
        (procedure)))
    
    (map (ignore countdown-from-10) '(1 1 1 1 1 1)) ; ==> (10 9 8 7 6 5)
    
    ;; reset
    (countdown-from-10 10)  ; ==> 9
    (countdown-from-10)     ; ==> 8
    ;; reset again
    (countdown-from-10 100) ; ==> 99
    
    0 讨论(0)
  • 2020-12-06 04:04

    Kudos to @Sylwester for a great answer.

    The difficult part is making yield available to the generator function. datum->syntax creates a syntax object, and requires you to provide another syntax object from which the context for the new object is taken. In this case, we can use stx which has the same context as the function passed into the macro.

    If people find it helpful, I use a simpler version:

    (define-syntax (set-continuation! stx)
      "Simplifies the common continuation idiom
        (call/cc (λ (k) (set! name k) <do stuff>))"
      (syntax-case stx ()
        [(_ name . body)
         #`(call/cc (λ (k)
                      (set! name k)
                      . body))]))
    
    (define-syntax (make-generator stx)
      "Creates a Python-like generator. 
       Functions passed in can use the `yield` keyword to return values 
       while temporarily suspending operation and returning to where they left off
       the next time they are called."
      (syntax-case stx ()
        [(_ fn)
         #`(let ((resume #f)
                 (break #f))
             (define #,(datum->syntax stx 'yield)
               (λ (v)
                 (set-continuation! resume
                   (break v))))
             (λ ()
               (if resume
                   (resume #f)
                   (set-continuation! break
                     (fn)
                     'done))))]))
    

    An example of its usage:

    (define countdown
      (make-generator
       (λ ()
         (for ([n (range 5 0 -1)])
               (yield n)))))
    
    (countdown)
    => 5
    ...
    (countdown)
    => 1
    (countdown)
    => 'done
    (countdown)
    => 'done
    
    0 讨论(0)
  • 2020-12-06 04:05

    One approach is here. If you are using guile, you should use prompts (they are about two orders of magnitude faster than using full continuations with guile):

    How to implement Python-style generator in Scheme (Racket or ChezScheme)?

    0 讨论(0)
提交回复
热议问题