Event-lang:   synchronizable event programming
1 Sequential Combinators
pure
return
args
args*
fmap
fmap*
app
app*
bind
bind*
seq
seq0
test
series
series*
reduce
reduce*
loop
loop*
2 Concurrent Combinators
async-set
async-set*
async-args
async-args*
async-fmap
async-fmap*
async-app
async-app*
async-bind
async-bind*
3 Synchronization Gates
gate?
gate
open-gate
gated
4 Racket base
4.1 Sequential
event-let
event-let*
event-cond
event-list
event-list*
event-map
4.2 Concurrent
async-let
async-list
async-list*
async-map
async-void
async-void*
6.12

Event-lang: synchronizable event programming

Eric Griffis <[email protected]>

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.383056640625)

'(2 . 0.341064453125)

'(3 . 0.405029296875)

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.219970703125)

'(1 . 0.259033203125)

'(3 . 0.277099609375)

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.

 (require event) package: event-lang

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 N 0)
> (define evt (pure (set! N (add1 N))))
> (sync evt)
> (sync evt)
> N

2

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 N 0)
> (define evt (return (set! N (add1 N))))
> (sync evt)
> (sync evt)
> N

1

procedure

(args E ...)  evt?

  E : evt?

procedure

(args* Es)  evt?

  Es : (listof evt?)
Returns a synchronizable event that evaluates 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 Es)  evt?

  f : (-> any/c ... any)
  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

(app F E ...)  evt?

  F : evt?
  E : evt?

procedure

(app* F Es)  evt?

  F : 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* Es f)  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 the synchronization result of E1 is not #f, it becomes E2. Otherwise, it becomes E3.

> (sync (test (pure #t) (pure 1) (pure 2)))

1

> (sync (test (pure #f) (pure 1) (pure 2)))

2

procedure

(series E f ...)  evt?

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

procedure

(series* E fs)  evt?

  E : 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)
    (compose return (curry + 2))
    (compose return (curry * 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 v ... or 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 v ... or 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 v ... or vs and continuing with the synchronization result of the event generated by applying f to the previous results. Never becomes ready for synchronization.

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

10

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)

'(2 3 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)))

132

'(1 2 3)

3 Synchronization 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.

4 Racket base

 (require event/base) package: event-lang

4.1 Sequential

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)

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)

4.2 Concurrent

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

231

1

2

3

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