Event-lang:   synchronizable event programming
1 Introduction
1.1 Some Examples
2 Event Combinators
2.1 The pure Form
2.2 Connecting Events to Functions
2.3 Argument Lists
2.4 Connecting Functions to Events
2.5 Connecting Events to Events
3 Synchronization Time
3.1 Duplicating channel-put-evt
3.2 Sequence Generators
4 Cooperative Concurrency
4.1 Synchronization Gates
4.2 Messaging Protocols
4.3 Interleaving Events
5 Reference
5.1 Sequential Combinators
pure
become
return
arg-list
args
args*
fmap
fmap*
join
app
app*
bind
bind*
seq
seq0
test
series
series*
reduce
reduce*
loop
loop*
memoize
promise
promises
promises*
5.2 Concurrent Combinators
async-set
async-set*
async-args
async-args*
async-fmap
async-fmap*
async-app
async-app*
async-bind
async-bind*
5.3 Gates
gate?
gate
open-gate
gated
5.4 The Racket API
5.4.1 Syntactic Forms
event-let
event-let*
event-cond
5.4.2 Pairs and Lists
event-list
event-list*
event-map
5.4.3 Concurrent Syntactic Forms
async-let
5.4.4 Concurrent Pairs and Lists
async-list
async-list*
async-map
async-void
async-void*
6.12

Event-lang: synchronizable event programming

Eric Griffis <[email protected]>

    1 Introduction

      1.1 Some Examples

    2 Event Combinators

      2.1 The pure Form

      2.2 Connecting Events to Functions

      2.3 Argument Lists

      2.4 Connecting Functions to Events

      2.5 Connecting Events to Events

    3 Synchronization Time

      3.1 Duplicating channel-put-evt

      3.2 Sequence Generators

    4 Cooperative Concurrency

      4.1 Synchronization Gates

      4.2 Messaging Protocols

      4.3 Interleaving Events

    5 Reference

      5.1 Sequential Combinators

      5.2 Concurrent Combinators

      5.3 Gates

      5.4 The Racket API

        5.4.1 Syntactic Forms

        5.4.2 Pairs and Lists

        5.4.3 Concurrent Syntactic Forms

        5.4.4 Concurrent Pairs and Lists

1 Introduction

Event-lang is a Racket library that simplifies the creation of complex synchronizable events. It provides a primitive expression lifting form,

> (pure 123)

#<evt>

some event combinators,

> (sync (fmap + (pure 1) (pure 2)))

3

> (sync (app (pure +) (pure 1) (pure 2)))

3

> (sync (bind (pure 1) (pure 2) (λ xs (pure (apply + xs)))))

3

and a collection of event-friendly alternatives to base Racket forms and functions.

> (sync
   (event-let
    ([x (pure 1)]
     [y (pure 2)])
    (pure (list x y))))

'(1 2)

Composite events make progress by synchronizing constituent events, either concurrently or in a predictable sequence. Synchronization results can be ordered as specified,

> (let ([t0 (current-inexact-milliseconds)])
    (define (now) (- (current-inexact-milliseconds) t0))
    (sync
     (async-args
      (pure (cons 1 (now)))
      (pure (cons 2 (now)))
      (pure (cons 3 (now))))))

'(1 . 0.18798828125)

'(2 . 0.15185546875)

'(3 . 0.173828125)

or as completed.

> (let ([t0 (current-inexact-milliseconds)])
    (define (now) (- (current-inexact-milliseconds) t0))
    (sync
     (async-set
      (pure (cons 1 (now)))
      (pure (cons 2 (now)))
      (pure (cons 3 (now))))))

'(2 . 0.099853515625)

'(3 . 0.114990234375)

'(1 . 0.126953125)

The project has three outstanding objectives:

  1. Provide a sophisticated lifting form to simplify usage of the provided constructs.

  2. Provide a full-blown #lang event/racket/base for producing whole modules of events and event constructors from ordinary Racket code in a principled manner.

  3. Provide support for static analysis of synchronization behaviors. Event programming in Racket is a curious form of meta-programming, and a few simple compile-time checks could reduce cognitive overhead.

1.1 Some Examples

Wait until a bunch of stuff is done

Use async-void to wait until all events are ready and then ignore the results.

> (sync (async-void (pure 1) (pure 2) (pure 3)))
> (sync (async-void (pure 4) (pure 5) (pure 6)))
Memoization

Record the synchronization results of an event and thereafter reproduce them immediately.

> (define (memoize evt)
    (define result #f)
    (define (save vs)
      (set! result (pure (apply values vs)))
    (become (or result (bind evt (λ vs (save vs) result))))))

Only the first successful sync has side effects.

> (define e1 (pure (write 'X)))
> (define e2 (memoize e1))
> (begin (sync e1) (sync e1) (sync e1))

XXX

> (begin (sync e2) (sync e2) (sync e2))

X

Promises

Capture the return values of a thunk in a background thread and then produce them as synchronization results.

> (define (promised thunk)
    (define ch (make-channel))
    (define thunk*
      (λ () (call-with-values thunk (curry channel-put ch))))
    (memoize (seq0 ch (thread thunk*))))

The results are memoized explicitly.

> (define p (promised (λ () (writeln 123) 4)))

123

> (sync p)

4

> (sync p)

4

The promise combinator is similar, but it takes an event instead of a thunk.

2 Event Combinators

2.1 The pure Form

The pure form wraps always-evt to create an event that evaluates an arbitrary expression at synchronization time. It is the lambda of event programming. The pure form can close over free variables in its body, and it can produce multiple values simultaneously, but it has no argument list.

The implementation of pure is dead simple.

> (define-syntax-rule (pure datum)
    (handle-evt always-evt (λ _ datum)))

The datum is injected into a lambda form and will be re-evaluated each time the event is successfully synchronized. To pre-evaluate datum, use return instead.

2.2 Connecting Events to Functions

Racket provides the basic event constructors: handle-evt, replace-evt, and guard-evt.

The handle-evt constructor extends the synchronization time of an event by applying a function to its synchronization result.

> (sync (handle-evt (pure (values 1 2 3)) +))

6

> (sync (handle-evt (pure (values 1 2 3)) (λ xs (map sub1 xs))))

'(0 1 2)

The replace-evt constructor composes an event with any function that produces another event. In the simplest case, this splices one event onto the end of another.

> (define (splice-evts e1 e2)
    (replace-evt e1 (λ _ e2)))
> (sync
   (splice-evts
    (pure (writeln 'E1))
    (pure (writeln 'E2))))

E1

E2

The guard-evt constructor invokes a thunk to produce an event at synchronization time.

> (define N 0)
> (define one-two-many
    (guard-evt
     (λ ()
      (set! N (add1 N))
      (return (if (< N 3) N 'many)))))
> (sync (event-list* (make-list 4 one-two-many)))

'(1 2 many many)

2.3 Argument Lists

The arg-list combinator takes a list of events and produces a list of their synchronization results.

> (define (arg-list evts)
    (foldr
     (λ (x ys)
       (replace-evt x (λ v (handle-evt ys (λ (vs) (append v vs))))))
     (pure null) evts))
> (sync (arg-list (list (pure 1) (pure 2) (pure (values 3 4)))))

'(1 2 3 4)

It’s just about as easy to do with direct recursion,

> (define (rec-arg-list evts [vs null])
    (if (null? evts)
        (return (reverse vs))
        (replace-evt (car evts)
                     (λ v (rec-arg-list (cdr evts) (append v vs))))))
> (sync
   (rec-arg-list (list (pure 1) (pure 2) (pure (values 3 4)))))

'(1 2 4 3)

or with a loop.

> (define (loop-arg-list evts)
    (let loop ([evts evts] [acc null])
      (if (null? evts)
          (return (apply append (reverse acc)))
          (replace-evt (car evts)
                       (λ vs (loop (cdr evts) (cons vs acc)))))))
> (sync
   (loop-arg-list (list (pure 1) (pure 2) (pure (values 3 4)))))

'(1 2 3 4)

The args combinator composes arg-list with values.

> (define (args . evts)
    (handle-evt (arg-list evts) (curry apply values)))
> (sync (args (pure 1) (pure 2) (pure 3)))

1

2

3

Constructors like args with a rest argument are handy in the REPL. In library code, functions with a final list argument can be easier to use. Some constructs have a starred variant that splices its last argument onto the others in the way list* does.

> (define (args* . evts)
    (apply args (append (drop-right evts 1) (last evts))))
> (sync (args* (pure 1) (list (pure 2) (pure 3))))

1

2

3

2.4 Connecting Functions to Events

The fmap combinator applies a function to the synchronization results of its arguments. The handle-evt constructor is essentially fmap, but handle-evt wants the entire argument list of the handler function to come from a single event. Providing an event for each argument is often easier.

> (define (fmap f . evts)
    (handle-evt (args* evts) f))
> (define calc1
    (match-lambda
     [`(,a + ,b) (fmap + (calc1 a) (calc1 b))]
     [`(,a * ,b) (fmap * (calc1 a) (calc1 b))]
     [(? number? n) (pure n)]))
> (sync (calc1 '((1 * 2) + (3 * 4))))

14

The bind combinator also applies a function to the synchronization results of its argument list, but the function must be an event constructor and it must be the last argument. The replace-evt constructor is essentially bind, but replace-evt wants the entire argument list of the event constructor to come from a single event. Again, giving each argument as a separate event is often easier.

> (define (bind . evts+f)
    (replace-evt (args* (drop-right evts+f 1)) (last evts+f)))
> (define (calc2 expr)
    (match expr
     [`(,a + ,b) (bind (calc2 a) (calc2 b) (compose return +))]
     [`(,a * ,b) (bind (calc2 a) (calc2 b) (compose return *))]
     [(? number? n) (pure n)]))
> (sync (calc2 '((1 * 2) + (3 * 4))))

14

The series constructor passes results to arguments in a series of arbitrary event combinators.

> (define (series evt . fs)
    (foldl (λ (f e) (replace-evt e f)) evt fs))
> (sync
   (series
    (pure (values 1 2 3))
    (λ xs (pure (values (apply + xs) 4)))
    (compose return *)))

24

The reduce constructor recursively applies a function to its results until the results satisfy a predicate.

> (define (reduce f check . xs)
    (define (pred ys) (apply check (append xs ys)))
    (define (recur ys) (apply reduce f check ys))
    (replace-evt
     (apply f xs)
     (λ ys (if (pred ys) (pure (apply values ys)) (recur ys)))))
> (define (two-to-the p)
    (reduce
     (λ (n k) (pure (values (* n 2) (+ k 1))))
     (λ (_ __ ___ k) (>= k p))
     1 0))
> (sync (two-to-the 10))

1024

10

> (sync (two-to-the 16))

65536

16

2.5 Connecting Events to Events

The become combinator synchronizes an event and then synchronizes the synchronization result.

> (define-syntax-rule (become expr)
    (join (pure expr)))

It gets its name from the actor model.

> (define (worker [N 0])
    (seq
     (thread-receive-evt)
     (become
      (worker (match (thread-receive)
                ['inc (add1 N)]
                ['dec (sub1 N)]
                [(? thread? t) (deliver t N) N])))))
> (define (deliver t msg)
    (fmap void (thread (λ () (thread-send t msg)))))
> (define a-printer (thread (λ () (writeln (thread-receive)))))
> (define a-worker (thread (λ () (sync (worker)))))
> (for ([_ 5]) (sync (deliver a-worker 'inc)))
> (for ([_ 2]) (sync (deliver a-worker 'dec)))
> (sync (seq (deliver a-worker a-printer) (fmap void a-printer)))

3

The app combinator applies the synchronization result of its first argument to the synchronization results of the remaining arguments.

> (define (app f-evt . evts)
    (replace-evt f-evt (λ (f) (fmap* f evts))))
> (sync (app (pure +) (pure 1) (pure 2)))

3

The seq combinator synchronizes one or more events and discards all but the last synchronization result.

> (define (seq evt . evts)
    (if (null? evts) evt (replace-evt evt (λ _ (apply seq evts)))))
> (sync (seq (pure 1) (pure 2) (pure 3)))

3

The seq0 combinator is similar, discarding all but the first synchronization result.

> (define (seq0 evt . evts)
    (replace-evt
     evt (λ vs (handle-evt (args* evts) (λ _ (apply values vs))))))
> (sync (seq0 (pure 1) (pure 2) (pure 3)))

1

The test combinator is a multi-valued if expression for events.

> (define (test test-evt then-evt else-evt)
    (replace-evt
     test-evt (λ vs (if (andmap values vs) then-evt else-evt))))

If none of the values produced by test-evt are #f, the test succeeds.

> (list
   (sync (test (pure (values 1 2)) (pure 'Tru) (pure 'Fls)))
   (sync (test (pure (values 3 #f)) (pure 'Tru) (pure 'Fls))))

'(Tru Fls)

3 Synchronization Time

The semaphore is a simple event-based thread synchronization mechanism. Suppose we wanted to create a semaphore that short-circuits after a few posts. We could use guard-evt to choose its behavior at each synchronization.

> (define (guarded-semaphore N)
    (define sema (make-semaphore))
    (define (next) (set! N (- N 1)) sema)
    (values sema (guard-evt (λ () (if (<= N 0) always-evt (next))))))

The become combinator can do the same thing without the lambda abstraction.

> (define (bounded-semaphore N)
    (define sema (make-semaphore))
    (define (next) (set! N (- N 1)) sema)
    (values sema (become (if (<= N 0) always-evt (next)))))

Th guarded-semaphore constructor returns two values: an actual semaphore for posting and a bounded reference for synchronizing. At first, sema and semb do the same thing. Each time sema receives a post, a thread waiting on semb wakes up. After sema receives N posts, all threads waiting on semb wake up and it becomes permanently ready for synchronization.

> (define-values (sema semb) (bounded-semaphore 2))
> (sync
   (async-void
    (thread (λ ()
              (writeln '(T1 X)) (semaphore-post sema)
              (writeln '(T1 Y)) (semaphore-post sema)))
    (thread (λ ()
              (sync semb) (writeln '(T2 A))
              (sync semb) (writeln '(T2 B))
              (sync semb) (writeln '(T2 C))))))

(T1 X)

(T2 A)

(T1 Y)

(T2 B)

(T2 C)

3.1 Duplicating channel-put-evt

Lists of events are easy to extend with map.

> (define (channel-dup-evt cs v)
    (async-void* (map (curryr channel-put-evt v) cs)))
> (define cs (build-list 5 (λ _ (make-channel))))
> (define ts
    (for/list ([c cs] [i 5]) ; read many times
      (thread (λ () (writeln (cons i (channel-get c)))))))
> (sync (seq (channel-dup-evt cs 'X) ; write once
             (async-void* ts)))

(0 . X)

(1 . X)

(2 . X)

(3 . X)

(4 . X)

3.2 Sequence Generators

The natural numbers

Close over a counter and increment it once per sync.

> (define nat
    (let ([n 0])
      (pure (begin0 n (set! n (add1 n))))))

Now we can use nat to get one number at a time.

> (sync nat)

0

> (sync nat)

1

> (sync nat)

2

nat is handy for generating indices and unique keys in bulk through repetition.

> (sync (event-list* (make-list 4 nat)))

'(3 4 5 6)

The Fibonacci sequence

The “hello world” of recursion.

> (define (naive-fib n)
    (case n
      [(0) (pure 0)]
      [(1) (pure 1)]
      [else (fmap + (naive-fib (- n 1)) (naive-fib (- n 2)))]))

Of course, the naive implementation is very slow.

> (time (sync (naive-fib 29)))

cpu time: 5826 real time: 5831 gc time: 1004

514229

This one is much faster:

> (define fib
    (let ([a 1] [b 0])
      (pure (begin0 b (set!-values (a b) (values (+ a b) a))))))
> (time (last (sync (event-list* (make-list 30 fib)))))

cpu time: 0 real time: 0 gc time: 0

514229

fib can be combined with nat to build an index.

> (define fibs (make-hash))
> (sync
   (async-void*
    (make-list 30 (fmap (curry hash-set! fibs) nat fib))))
> (hash-ref fibs 29)

514229

> (hash-ref fibs 15)

610

4 Cooperative Concurrency

4.1 Synchronization Gates

4.2 Messaging Protocols

4.3 Interleaving Events

5 Reference

 (require event) package: event-lang

5.1 Sequential Combinators

syntax

(pure datum)

Lifts datum into a into a synchronizable event. Delays evaluation of datum until a thread synchronizes on it. The synchronization result is the evaluation result.

> (define evt (pure (writeln (+ 1 2))))
> (sync evt)

3

> (sync evt)

3

syntax

(become expr)

Lifts an event-producing expr into a synchronizable event that immediately replaces itself with the event produced by evaluating expr.

> (sync (become (pure 123)))

123

procedure

(return v)  evt?

  v : any/c
 = (pure v)
Evaluates v and then lifts the result into an event. Returns a synchronizable event that does nothing and uses v as its synchronization result.

> (define evt (return (writeln (+ 1 2))))

3

> (sync evt)
> (sync evt)

procedure

(arg-list Es)  evt?

  Es : (listof evt?)
Returns a synchronizable event that synchronizes Es in order. The synchronization result is a list of the synchronization results of the Es.

> (sync (arg-list (list (pure 1) (pure 2) (pure 3))))

'(1 2 3)

procedure

(args E ...)  evt?

  E : evt?

procedure

(args* E ... Es)  evt?

  E : evt?
  Es : (listof evt?)
Returns a synchronizable event that synchronizes Es in order and then applies values to the synchronization results.

> (sync (args (pure 1) (pure 2) (pure 3)))

1

2

3

procedure

(fmap f E ...)  evt?

  f : (-> any/c ... any)
  E : evt?

procedure

(fmap* f E ... Es)  evt?

  f : (-> any/c ... any)
  E : evt?
  Es : (listof evt?)
Returns a synchronizable event that synchronizes Es in order and then applies f to the synchronization results.

> (sync (fmap + (pure 1) (pure 2) (pure 3)))

6

procedure

(join E)  evt?

  E : evt?
Returns a synchronizable event that synchronizes E and then synchronizes the synchronization result of E.

> (sync (join (pure (pure 123))))

123

procedure

(app F E ...)  evt?

  F : evt?
  E : evt?

procedure

(app* F E ... Es)  evt?

  F : evt?
  E : evt?
  Es : (listof evt?)
Returns a synchronizable event that synchronizes F and Es in order and then applies the synchronization result of the former to the synchronization results of the latter.

> (sync (app (pure +) (pure 1) (pure 2) (pure 3)))

6

procedure

(bind E ... f)  evt?

  E : evt?
  f : (-> any/c ... evt?)

procedure

(bind* E ... Es f)  evt?

  E : evt?
  Es : (listof evt?)
  f : (-> any/c ... evt?)
Returns a synchronizable event that synchronizes Es in order and then becomes the event returned from f applied to the synchronization results.

> (sync
   (bind
    (pure 1)
    (pure 2)
    (pure 3)
    (compose return +)))

6

procedure

(seq E ...+)  evt?

  E : evt?
Returns a synchronizable event that synchronizes Es in order and then uses the final synchronization result as its own.

> (sync (seq (pure 1) (pure 2) (pure 3)))

3

procedure

(seq0 E ...+)  evt?

  E : evt?
Returns a synchronizable event that synchronizes Es in order and then uses the first synchronization result as its own.

> (sync (seq0 (pure 1) (pure 2) (pure 3)))

1

procedure

(test E1 E2 E3)  evt?

  E1 : evt?
  E2 : evt?
  E3 : evt?
Returns a synchronizable event that becomes either E2 or E3. If no value in the synchronization result of E1 is #f, it becomes E2. Otherwise, it becomes E3.

> (list
   (sync (test (pure #t) (pure 'Tru) (pure 'Fls)))
   (sync (test (pure #f) (pure 'Tru) (pure 'Fls)))
   (sync (test (pure (values #t #t)) (pure 'Tru) (pure 'Fls)))
   (sync (test (pure (values #t #f)) (pure 'Tru) (pure 'Fls)))
   (sync (test (pure (values #f #t)) (pure 'Tru) (pure 'Fls)))
   (sync (test (pure (values #f #f)) (pure 'Tru) (pure 'Fls)))
   (sync (test (pure (values)) (pure 'Tru) (pure 'Fls))))

'(Tru Fls Tru Fls Fls Fls Tru)

procedure

(series E f ...)  evt?

  E : evt?
  f : (-> any/c evt)

procedure

(series* E f ... fs)  evt?

  E : evt?
  f : (-> any/c evt)
  fs : (listof (-> any/c evt?))
Returns a synchronizable event that applies fs in series, starting with the synchronization result of E and continuing with the synchronization result of the event generated by the previous f. Uses the final synchronization result as its own.

> (sync
   (series
    (pure 1)
    (λ (x) (return (+ x 2)))
    (λ (x) (return (* x 3)))))

9

procedure

(reduce f check v ...)  evt?

  f : (-> any/c ... evt?)
  check : (-> any/c ... boolean?)
  v : any/c

procedure

(reduce* f check vs)  evt?

  f : (-> any/c ... evt?)
  check : (-> any/c ... boolean?)
  vs : (listof any/c)
Returns a synchronizable event that applies f to a set of values recursively, starting with vs and continuing with the synchronization result of the event generated by applying f to the previous results. Applies check to an argument list created by appending vs onto the results of f. Becomes ready for synchronization when check returns #t. Uses the final synchronization result as its own.

> (sync
   (reduce
    (λ (x) (pure (add1 x)))
    (λ (x y) (>= y 10))
    0))

10

procedure

(loop f v ...)  evt?

  f : (-> any/c ... evt?)
  v : any/c

procedure

(loop* f vs)  evt?

  f : (-> any/c ... evt?)
  vs : (listof any/c)
Returns a synchronizable event that applies f to a value recursively, starting with vs and continuing with the synchronization result of the event generated by applying f to the previous results.

> (with-handlers ([number? values])
    (sync
     (loop (λ (x) (if (< x 10) (pure (+ x 1)) (raise x)))
           0)))

10

procedure

(memoize E)  evt?

  E : evt?
Returns a synchronizable event that syncs E and remembers the result. The synchronization result of the whole event is the synchronization result of E.

> (define e1 (pure (begin (writeln '!!) (+ 1 2))))
> (sync e1)

!!

3

> (sync e1)

!!

3

> (define e2 (memoize e1))
> (sync e2)

!!

3

> (sync e2)

3

> (sync e2)

3

procedure

(promise E)  evt?

  E : evt?
Syncs E in a background thread. Returns a synchronizable event that becomes ready for synchronization when the background thread finishes. The synchronization result of the whole event is the synchronization result of E.

> (define ch (make-channel))
> (define ps (for/list ([_ 10]) (promise ch)))
> (for ([i 10]) (channel-put ch i))
> (map sync ps)

'(4 7 3 8 2 6 1 9 0 5)

The synchronization result of a promise is memoized. Attempting to synchronize a finished promise immediately produces the original result without any side effects.

procedure

(promises E ...)  evt?

  E : evt?

procedure

(promises* Es)  evt?

  Es : (listof evt?)
Syncs each of the Es in a separate background thread. Returns a synchronizable event that becomes ready for synchronization when all of the background threads finish. The synchronization result of the whole event is a list of the synchronization results of the Es.

> (define ch (make-channel))
> (define ps (promises* (make-list 10 ch)))
> (for ([i 10]) (channel-put ch i))
> (sync ps)

'(4 7 3 8 2 6 1 9 0 5)

The synchronization results of the promises are memoized. Attempting to synchronize finished promises immediately produce the original results without any side effects.

5.2 Concurrent Combinators

procedure

(async-set E ...)  evt?

  E : evt?

procedure

(async-set* Es)  evt?

  Es : (listof evt?)
Returns a synchronizable event that synchronizes E ... or Es concurrently and then applies values to a list of the synchronization results in order of completion.

> (define evt
    (handle-evt (async-set (pure 1) (pure 2) (pure 3)) list))
> (sync evt)

'(2 1 3)

> (sync evt)

'(3 2 1)

> (sync evt)

'(3 2 1)

procedure

(async-args E ...)  evt?

  E : evt?

procedure

(async-args* Es)  evt?

  Es : (listof evt?)
Returns a synchronizable event that evaluates E ... or Es concurrently and then applies values to a list of the synchronization results in the order defined.

> (define evt
    (handle-evt
     (async-args (pure 1) (pure 2) (pure 3))
     list))
> (sync evt)

'(1 2 3)

procedure

(async-fmap f E ...)  evt?

  f : (-> any/c ... any)
  E : evt?

procedure

(async-fmap* f Es)  evt?

  f : (-> any/c ... any)
  Es : (listof evt?)
Returns a synchronizable event that synchronizes E ... or Vs concurrently and then applies f to a list of the synchronization results.

> (sync (async-fmap + (pure 1) (pure 2) (pure 3)))

6

procedure

(async-app F E ...)  evt?

  F : evt?
  E : evt?

procedure

(async-app* F Es)  evt?

  F : evt?
  Es : (listof evt?)
Returns a synchronizable event that synchronizes F and E ... or Es concurrently and then applies the synchronization result of the former to a list of the synchronization results of the latter.

> (sync (async-app (pure +) (pure 1) (pure 2) (pure 3)))

6

procedure

(async-bind E ... f)  evt?

  E : evt?
  f : (-> any/c ... evt?)

procedure

(async-bind* Es f)  evt?

  Es : (listof evt?)
  f : (-> any/c ... evt?)
Returns a synchronizable event that synchronizes E ... or Es concurrently and then becomes the event returned from f applied to a list of the synchronization results.

> (sync
   (async-bind
    (seq (pure (print 1)) (pure 1))
    (seq (pure (print 2)) (pure 2))
    (seq (pure (print 3)) (pure 3))
    (compose return list)))

123

'(1 2 3)

5.3 Gates

 (require event/gate) package: event-lang

A gate is a simple primitive for synchronizing many threads at once. A gate is either opened or closed and is closed initially. Threads synchronizing on a closed gate will block until the gate is opened. Once a gate is opened, it cannot be closed.

procedure

(gate? v)  boolean?

  v : any/c
Returns #t if v is a gate, #f otherwise.

procedure

(gate)  gate?

Creates and returns a new closed gate.

procedure

(open-gate g)  evt?

  g : gate?
Returns a synchronizable event that simultaneously unblocks all threads attempting to synchronize on the gate. Becomes ready for synchronization when the gate is opened.

procedure

(gated g E)  evt?

  g : gate?
  E : evt?
Returns a synchronizable event that synchronizes E and becomes ready for synchronization when g is opened. The synchronization result is the synchronization result of E.

5.4 The Racket API

 (require event/racket) package: event-lang

5.4.1 Syntactic Forms

syntax

(event-let ([id val-evt] ...) body-evt ...+)

Creates a synchronizable event that synchronizes the val-evts from left to right and binds the ids to the results, then synchronizes the body-evts. Uses the synchronization result of its final body-evt as its own.

> (sync
   (event-let ([x (pure 1)]
               [y (pure 2)])
     (pure (+ x y))))

3

syntax

(event-let* ([id val-evt] ...) body-evt ...+)

Like event-let, but synchronizes the val-evts one by one, binding each id as soon as the value is available. The ids are bound in the remaining val-evts as well as the bodys, and the ids need not be distinct; later bindings shadow earlier bindings.

Creates a synchronizable event that Synchronizes the val-evts from left to right and binds the ids to the results, then synchronizes the body-evts. Uses the synchronization result of its final body-evt as its own.

> (sync
   (event-let* ([x (pure 1)]
                [y (pure (+ x 2))])
     (pure (+ x y))))

4

syntax

(event-cond event-cond-clause ...)

 
event-cond-clause = [test-evt then-body-evt ...+]
  | [else then-body-evt ...+]
  | [test-evt => proc-evt]
  | [test-evt]
Creates a synchronizable event. If no event-cond-clauses are present, the synchronization result is #<void>.

An event-cond-clause that starts with else must be the last event-cond-clause.

If only a [else then-body-evt ...+] is present, then the then-body-evts are synchronized. The synchronization result from all but the last then-body-evt are ignored. The synchronization result of the last then-body-evt is the synchronization result for the whole event-cond form.

Otherwise, the first test-evt is synchronized. If it produces #f, then the synchronization result is the same as an event-cond form with the remaining event-cond-clauses.

[test-evt then-body-evt ...+]

The then-body-evts are synchronized in order, and the synchronization result from all but the last then-body-evt are ignored. The synchronization result of the last then-body-evt provides the result for the whole event-cond form.

[test-evt => proc-evt]

The proc-evt is synchronized, and it must produce a procedure that accepts one argument, otherwise the exn:fail:contract exception is raised. The procedure is applied to the synchronization result of test-evt. The synchronization result for the whole event-cond form is the values returned by the procedure call.

[test-evt]

The synchronization result of test-evt is provided as the synchronization result of the event-cond form.

Examples:
> (sync (event-cond))
> (sync (event-cond [else (pure 5)]))

5

> (sync
   (event-cond
    [(pure (positive? -5)) (pure (error "doesn't get here"))]
    [(pure (zero? -5)) (pure (error "doesn't get here, either"))]
    [(pure (positive? 5)) (pure 'here)]))

'here

> (sync
   (event-cond
    [(pure (member 2 '(1 2 3))) => (pure (lambda (l) (map - l)))]))

'(-2 -3)

> (sync (event-cond [(pure (member 2 '(1 2 3)))]))

'(2 3)

5.4.2 Pairs and Lists

procedure

(event-list E ...)  evt?

  E : evt?

procedure

(event-list* E ... Es)  evt?

  E : evt?
  Es : (listof evt?)
Returns a synchronizable event that synchronizes all Es in order and then uses a list of the results as its synchronization result.

> (sync (event-list (pure 1) (pure 2) (pure 3)))

'(1 2 3)

procedure

(event-map f Es ...+)  evt?

  f : procedure?
  Es : (listof evt?)
Returns a synchronizable event that synchronizes the elements of the Es lists and applies f to the synchronization results of the elements, from the first elements to the last. The f argument must accept the same number of arguments as the number of supplied Ess, and all Ess must have the same number of elements. The synchronization result is a list containing each result of f in order.

> (sync
   (event-map
    +
    (list (pure 1) (pure 2) (pure 3))
    (list (pure 4) (pure 5) (pure 6))))

'(5 7 9)

5.4.3 Concurrent Syntactic Forms

syntax

(async-let ([x Ex] ...) E ...+)

Produces a synchronizable event that synchronizes Exs concurrently, binds the synchronization results to xs internally, and synchronizes the Es. The synchronization results from all but the last E are ignored. The synchronization result of the last E is the synchronization result for the whole async-let form.

> (sync
   (async-let
       ([x (seq (pure (print 1)) (pure 1))]
        [y (seq (pure (print 2)) (pure 2))]
        [z (seq (pure (print 3)) (pure 3))])
     (pure (values x y z))))

132

1

2

3

5.4.4 Concurrent Pairs and Lists

procedure

(async-list E ...)  evt?

  E : evt?

procedure

(async-list* E ... Es)  evt?

  E : evt?
  Es : (listof evt?)
Returns a synchronizable event that synchronizes all Es simultaneously and becomes ready for synchronization when all the Es are ready. The synchronization result is a list of the results, in order.

> (sync (async-list (pure 1) (pure 2) (pure 3)))

'(1 2 3)

procedure

(async-map f Es ...+)  evt?

  f : procedure?
  Es : (listof evt?)
Returns a synchronizable event that synchronizes Es lists simultaneously and then applies f to the synchronization results of the elements, from the first elements to the last. The f argument must accept the same number of arguments as the number of supplied Ess, and all Ess must have the same number of elements. The synchronization result is a list containing each result of f in order.

> (sync
   (async-map
    +
    (list (pure 1) (pure 2) (pure 3))
    (list (pure 4) (pure 5) (pure 6))))

'(5 7 9)

procedure

(async-void E ...)  evt?

  E : evt?

procedure

(async-void* E ... Es)  evt?

  E : evt?
  Es : (listof evt?)
Returns a synchronizable event that synchronizes all Es simultaneously and becomes ready for synchronization when all the Es are ready. The synchronization result is a single void.

> (sync (async-void (pure 1) (pure 2) (pure 3)))