Реализовать выход и отправить в схеме

Я пытаюсь перенести yield и yield from с Python на Scheme.

Вот реализация, которую я сделал:

(define (coroutine routine)
  (let ((current routine)
    (status 'new))
    (lambda* (#:optional value)
      (let ((continuation-and-value
         (call/cc (lambda (return)
            (let ((returner
                   (lambda (value)
                 (call/cc (lambda (next)
                        (return (cons next value)))))))
              (if (equal? status 'new)
                  (begin
                (set! status 'running)
                (current returner))
                  (current (cons value returner)))
              (set! status 'dead))))))
    (if (pair? continuation-and-value)
        (begin (set! current (car continuation-and-value))
           (cdr continuation-and-value))
        continuation-and-value)))))

Проблема с этой реализацией заключается в том, что способ ее вызова не похож на Python yield.

(define why (call/cc (lambda (yield)
               (format #t "love me or leave me!")
               (yield "I leave!")
               ;; the program never reach this part
               (format #t "it probably left :("))))
(format #t "return actually populates WHY variable\n")
(format #t "WHY: ~a\n")

Среди прочего, каждый раз, когда мне нужно перезапустить сопрограмму, я должен let создать новую return переменную, чтобы иметь возможность exit сопрограммы. В принципе, я нахожу синтаксис слишком многословным. Есть ли другой, чтобы иметь более чистый синтаксис?

Должна быть возможность yield и send значений для сопрограммы. Вот пример того, как должна использоваться сопрограмма:

(define-coroutine (zrange start step)
  "compute a range of values starting a START with STEP between
   each value. The coroutine must be restarted with 0 or more, which
   is added to the step"
  (let loop ((n start))
    (loop (+ n step (yield n)))))


(coroutine-map (zrange 0 10) '(1 100 1000 10000 100000))
;; => 0 110 1120 11130 111140

В приведенном выше примере 1 игнорируется, а затем 100, 1000 передаются send генератору. Я сделал реализацию на основе кода @sylwester, но у меня проблемы с макросом:

(define (make-generator procedure)
  (define last-return #f)
  (define last-value #f)
  (define last-continuation (lambda (_) (procedure yield)))

  (define (return value)
    (newline)(display "fuuu")(newline)
    (call/cc (lambda (continuation)
               (set! last-continuation continuation)
               (set! last-value value)
               (last-return value))))
  (lambda* (. rest)  ; ignore arguments
    (call/cc (lambda (yield)
               (set! last-return yield)
               (apply last-continuation rest)))))

(define-syntax define-coroutine
  (syntax-rules ()
    ((_ (name args ...) body ...)
     (define (name args ...)

       (make-generator
        (lambda (yield)
          body ...))))))

(define-coroutine (zrange start step)
  (let loop ((n start))
     (loop (+ n step (yield n)))))

(display (map (zrange 0 10) '(1 100 1000 10000 100000)))

person amirouche    schedule 03.06.2015    source источник
comment
Что такое coroutine-map? где в zrange вы берете аргумент?   -  person Sylwester    schedule 08.06.2015
comment
Какой аргумент? yield не является аргументом zrange. Я думаю, что это требует негигиеничных макросов.   -  person amirouche    schedule 08.06.2015
comment
coroutine-map перебирает значения, возвращаемые (zrange 0 10), пока не возникнет ошибка.   -  person amirouche    schedule 08.06.2015
comment
Как ваш coroutine-map узнает, что он должен + соединить элементы вместе? Что делать, если вы хотите умножить? с аргументами, которые я имею в виду send, можете ли вы отправить больше значений в zrange, если он имеет конечную длину? Будет ли это как yielding каждый по порядку внизу?   -  person Sylwester    schedule 08.06.2015
comment
когда вы send что-то делаете, генератор перезапускается и yield возвращает отправленное значение. Вот почему (+ n step (yield n)) становится (+ 0 10 100). Я просто понял, что первое значение карты не учитывается в моей реализации. Я добавлю реализацию, которую я сделал.   -  person amirouche    schedule 08.06.2015
comment
Из соображений гигиены yield, представленный в вашем макросе, отличается от того, что находится в теле. Вместо ( . rest) вы просто хотите rest. Я думаю, что знаю, чего вы хотите, поэтому я обновлю свой ответ.   -  person Sylwester    schedule 08.06.2015
comment
Этот вопрос был введен в заблуждение, см. stackoverflow.com/q/56317339/140837   -  person amirouche    schedule 27.05.2019


Ответы (3)


Что-то вроде этого:

(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))))))

Используется следующим образом:

(define test 
 (make-generator
   (lambda (collect)
     (collect 1)
     (collect 5)
     (collect 10)
     #f)))

(test) ; ==> 1
(test) ; ==> 5
(test) ; ==> 10
(test) ; ==> #f (procedure finished)

Теперь мы можем обернуть внутренности в макрос:

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

Обратите внимание, что define-coroutine реализован с использованием синтаксиса-регистра, поскольку нам нужно сделать yield негигиеничным.

(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
person Sylwester    schedule 03.06.2015
comment
что такое collect? В противном случае, это в основном то, что я ищу. - person amirouche; 08.06.2015
comment
Отсутствует функция, я добавлю ее в вопрос. - person amirouche; 08.06.2015
comment
@amirouche make-generator принимает процедуру, которая принимает процедуру yield в качестве аргумента, точно так же, как call/cc принимает процедуру, которая принимает в качестве аргумента процедуру продолжения. Таким образом, вы можете выбрать в своем генераторе, что вы хотите, чтобы yield вызывалось, поскольку вы используете любое имя аргумента. - person Sylwester; 08.06.2015
comment
Теперь я понимаю. Гайл жаловался на устаревшую функцию. Я добавил пример того, что я именно ищу. - person amirouche; 08.06.2015
comment
Спасибо все нормально! Я попытаюсь сделать реализацию с помощью Shift Reset самостоятельно. - person amirouche; 10.06.2015
comment
@amirouche Помните, что shift и reset не являются частью стандарта Scheme, поэтому вы будете делать что-то, что нельзя переносить между реализациями. например, он не будет работать на Racket, поскольку у них есть собственные имена функций продолжения с разделителями. - person Sylwester; 10.06.2015

Один из подходов здесь. Если вы используете хитрость, вы должны использовать подсказки (они примерно на два порядка быстрее, чем использование полных продолжений с хитростью):

Как реализовать стиль Python генератор в схеме (Racket или ChezScheme)?

person Chris Vine    schedule 10.06.2015
comment
Спасибо. Я думал, что shift/reset был лучшим способом заменить call/cc? - person amirouche; 10.06.2015
comment
Как правило, для разделенных продолжений с хитростью подсказки - это путь, и я сомневаюсь, что ваша собственная реализация с использованием сдвига/сброса будет улучшением по сравнению с вызовом с подсказкой хитрости и прерыванием до подсказки. подсказки - это способ, которым guile реализует исключения, а также продолжения escape. Я должен начать с подсказок guile и посмотреть, сможете ли вы их улучшить, но для этого использования (генераторы сопрограмм) я сомневаюсь, что вы это сделаете. - person Chris Vine; 10.06.2015

Спасибо @Sylwester за отличный ответ.

Сложность заключается в том, чтобы сделать yield доступным для функции генератора. datum->syntax создает синтаксический объект и требует, чтобы вы предоставили другой синтаксический объект, из которого берется контекст для нового объекта. В этом случае мы можем использовать stx, который имеет тот же контекст, что и функция, переданная в макрос.

Если люди находят это полезным, я использую более простую версию:

(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))))]))

Пример его использования:

(define countdown
  (make-generator
   (λ ()
     (for ([n (range 5 0 -1)])
           (yield n)))))

(countdown)
=> 5
...
(countdown)
=> 1
(countdown)
=> 'done
(countdown)
=> 'done
person QuesterZen    schedule 08.02.2019
comment
Разновидность этого трюка используется макросом struct для определения новых имен в глобальной среде, и я также использую его довольно часто. Если у вас есть DrRacket, я бы порекомендовал посмотреть исходный код некоторых макросов: они хорошо написаны, и вы можете почерпнуть множество приемов, которым нелегко научиться из документации Scheme или Racket. См. stackoverflow.com/questions /20931806/ - person QuesterZen; 08.02.2019