[Top][All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

syntactic suger for coroutines

From: Stefan Israelsson Tampe
Subject: syntactic suger for coroutines
Date: Sat, 09 Mar 2013 20:59:41 +0100
User-agent: KMail/4.9.5 (Linux/3.5.0-25-generic; KDE/4.9.5; x86_64; ; )

Hi all,

Delimited continuations are really cool, and we all love them. I tried
to play with them to explore syntactical sugaring for coroutines. And
this is what I have right now. I do want these constructs to, in case
of it beeing possible, the code beeing inlined inside code with good 
performance to do this the construct suffers from a
defficiency. E.g. the tags are hidden and only lexical constructs are
possible. Let's have a play ...

The raw syntax basic block looks likelooks like,

  (with-coroutines-raw ((nm thunk 
                            (   (exit (pat transition-code ... ) ...)
     code ...) 

We see that we played with the letrec ideom here, nm will be functions
bound like in a letrec, but we have defined a set of exit-codes
e.g. (exit ...). inside thunk we issue (abort-to-ptompt TAG Exit-code
. args). then args are matched against pat and if there is a match
then the transition code is executed. the match is done with 
(match args (pat ...) ...), where match is the ice-9 matcher. 

Now I don't expect people to use this ideam. In stead I designed the
following more simplified structure.

   (with-coroutines ((nm lambda) ...) code ...)

This is exactly like the code for a letrec and indeed nm will be
lambdas. and the names are available inside the definition of the
lambda. The general rule is to not to execute the name inside the
lambda part instead one should use either of,

goto          : (goto nm)    
jumps to the nm code, resetting the current nm lambda to the initial 

goto-from     : (goto nm nm-to)    
jumps to the nm-to code, resetting the named nm lambda to the initial
state up the stack

gosub         :  (gosub nm)
jumps to the nm code, expecting a (return data ..) somewhere
further down in order to resume the funciton. Gosub can return values
just as with abort-to-prompt. gosub and goto can be intermixed.

gosub-from    :  (gosub nm nm-to)
As gosub but we specify which for we will reset.

continue      :  (continue nm x ...)
continue-from :  (continue-from nm nm-to x ...)
Like goto but if we goto back to this function it will continue from
the last state

return        : (return x ...)
reset the nm form to the initial state and return to the gosub with
arguments x ...

return-from   : (return-from nm x ...)
Return from the named nm, effectively reseting that form and jump back
to the gosub with arguments x ...

Example, with using with-corotines we can make 
   (tagbody (label: code ...) ...) and have,

  (pk 'at-a)
  (gosub c:)
  (pk 'at-a))

  (pk 'at-b)
  (goto d:))

  (pk 'at-c)

  (pk 'leave-at-d)))

;; (at-a)

;;; (at-c)

;;; (at-a)

;;; (at-b)

;;; (leave-at-d)

resume       : (resume x ...)
resume-from  : (resume nm x ...)
This will as with return goback to the gosub, but it will resume the
state when called again.
yield        : (yield x ...)
yield-from   : (yield nm x ...)
This will return from the current or named lambda (values x ...) and
keep the state so that we can continue, really nice for generators.

consume       : (consume)                   | (consume v)
consume-from  : (consume-from nm v)         | (consume-from nm)
This is like yield, but it will read in values to feed into elements
(consume) will work just like (abort-to-prompt) but with (consume v)
one would only consume one value. If the nm was applied with multiple 
values (a b c)  then the first consume will fetch (a b c) and return
b, the next (consume) will return b and the next (consume) will return
c without leaving the function which is a nice way of not needing to
reinstate the continuation to often. To define v use with feeder.

transit      : (transit () x ...) | (transit (v) x ...)
transit-from : (transit-from nm () x ...) | (transit-from nm (v) x ...)
A combined consume and yield

Example code:
;; Consider the definitions,
(define-syntax-rule (define-coroutine (name . args) code ...)
  (define name
    (lambda args
      (with-coroutines ((it (lambda () code ...)))

(define-syntax-rule (define-coroutine-inited (name . args) code ...)
  (define name
    (lambda args
      (with-coroutines ((it (lambda () code ...)))
        (it) it))))

;; then we can issue code like:
(define-coroutine (from n)
  (let loop ((i n))
    (yield i)
    (loop (+ i 1))))

(define-coroutine (in-list l)
  (let loop ((li l))
    (if (pair? li)
          (yield #t (car li))
          (loop  (cdr li)))

(define-coroutine-inited (collector l)
  (with-feed (q)
    (let loop ((li l))
      (if (consume q)
          (loop (cons (consume q) li))
          (reverse li)))))

(let ((collect (collector '(start)))
      (numbers (from 10))
      (items   (in-list '(a b c d e f g h i j k l m n o p))))
  (let loop ()
    (call-with-values items
      (lambda (p . x)
        (if p
              (feed collect #t (car x))
            (pk (feed collect #f)))))))

;;; ((start a b c d e f g h i j k l m n o p))

This is still an experimentation in progress, but you can follow it at

Also, what is exating is that this construct has with the addition of
rtl bytecode have very great potential of ending up in code that can
be optimized to read very performant code.

Have fun

reply via email to

[Prev in Thread] Current Thread [Next in Thread]