Next: , Previous: , Up: loops generators   [Index]


1.16.4.6 Dispatched generators

Generator Syntax: : vars arg0 arg ...

First the arg expressions are evaluated into a[1], a[2], …, a[n] and then a global dispatch procedure is used to dispatch on the number and types of the arguments and run the resulting generator. Initially the following cases are recognized, with i \in {1, …, n}:

:list

If for all i:

(list? a[i]) ⇒ #t
:string

If for all i:

(string? a[i]) ⇒ #t
:vector

If for all i:

(vector? a[i]) ⇒ #t
:range

If n \in {1, …, 3} and for all i \in {1, …, n}:

(and (integer? a[i])
     (exact?   a[i]))
⇒ #t
:real-range

If n \in {1, …, 3} and for all i \in {1, …, n}:

(real? a[i]) ⇒ #t
:char-range

If n = 2 and for all i \in {1, 2}:

(char? a[i]) ⇒ #t
:port

If n \in {1, 2} and:

(and (input-port? a[1])
     (procedure?  a[2]))
⇒ #t

The current dispatcher can be retrieved as (:-dispatch-ref), a new dispatcher ‘d’ can be installed by (:-dispatch-set! d) yielding an unspecified result, and a copy of the initial dispatcher can be obtained as (make-initial-:-dispatch). Please refer to the section below for recommendation how to add cases to the dispatcher.

Generator Syntax: :dispatched vars dispatch arg0 arg ...

Runs the variables through a sequence defined by dispatch and the arg expressions. The purpose of ‘:dispatched’ is implementing dispatched generators, in particular the predefined dispatching generator ‘:’.

The working of ‘:dispatched’ is as follows: First dispatch and the arg expressions are evaluated, resulting in a procedure d (the “dispatcher”) and the values a[1], a[2], …, a[n]. Then:

(d (list a[1] a[2] ... a[n]))

is evaluated, resulting in a value g. If g is not a procedure then the dispatcher did not recognize the argument list and an error is raised. Otherwise the “generator procedure” g is used to run vars through a sequence of values.

The sequence defined by g is obtained by repeated evaluation of (g empty) until the result is empty. In other words, g indicates the end of the sequence by returning its only argument, for which the caller has provided an object distinct from anything g can produce.

The definition of dispatchers is greatly simplified by the macro :generator-proc that constructs a generator procedure from a typed generator. Let (g var arg0 arg ...) be an instance of the ?generator syntax, for example an application–specific typed generator, with a single variable var and no index variable. Then:

(:generator-proc (g arg0 arg ...))
⇒ g

where the generator procedure g runs through the list:

(list-ec (g var arg0 arg ...) var)

In order to define a new dispatching generator (say :my) first a dispatching procedure (say :my-dispatch) is defined. The dispatcher will be called with a single (!) argument containing the list of all values to dispatch on. To enable informative error messages, the dispatcher should return a descriptive object (e.g. a symbol for the module name) when it is called with the empty list. Otherwise (if there is at least one value to dispatch on), the dispatcher must either return a generator procedure or #f (which means: no interest). As an example, the following skeleton code defines a dispatcher similar to the initial dispatcher of ::

(define (:my-dispatch args)
  (case (length args)
    ((1) (let ((a1 (car args)))
           (cond
            ((list? a1)
             (:generator-proc (:list a1)))
            ((string? a1)
             (:generator-proc (:string a1)))
            ...more unary cases...
            (else
             #f))))
    ((2) (let ((a1 (car args))
               (a2 (cadr args)))
           (cond
            ((and (list? a1) (list? a2))
             (:generator-proc (:list a1 a2)))
            ...more binary cases...
            (else
             #f))))
    ...more arity cases...
    (else
     (cond
      ((every?-ec (:list a args) (list? a))
       (:generator-proc (:list (apply append args))))
      ...more large variable arity cases...
      (else
       #f)))))

Once the dispatcher has been defined, the following macro implements the new dispatching generator:

(define-syntax :my
  (syntax-rules (index)
    ((:my cc var (index i) arg0 arg ...)
     (:dispatched cc var (index i) :my-dispatch arg0 arg ...))
    ((:my cc var arg0 arg ...)
     (:dispatched cc var :my-dispatch arg0 arg ...))))

This method of extension yields complete control of the dispatching process. Other modules can only add cases to ‘:my’ if they have access to ‘:my-dispatch’.

Extending the predefined dispatched generator

An alternative to adding a new dispatched generator is to extend the predefined generator ‘:’. Technically, extending ‘:’ means installing a new global dispatching procedure using ‘:-dispatch-set!’ as described above. In most cases, however, the already installed dispatcher should be extended by new cases. The following procedure is a utility for doing so:

(dispatch-union d1 d2)
⇒ d

where the new dispatcher d recognizes the union of the cases recognized by the dispatchers d1 and d2. The new dispatcher always tries both component dispatchers and raises an error in case of conflict. The identification returned by (d) is the concatenation of the component identifications (d1) and (d2), enclosed in lists if necessary. For illustration, consider the following code:

(define (example-dispatch args)
  (cond
   ((null? args)
    'example)
   ((and (= (length args) 1) (symbol? (car args)) )
    (:generator-proc (:string (symbol->string (car args)))))
   (else
    #f)))

(:-dispatch-set! (dispatch-union (:-dispatch-ref) example-dispatch))

After evaluation of this code, the following example will work:

(list-ec (: c 'abc)
  c)
⇒ (#\a #\b #\c)

Adding cases to ‘:’ is particularly useful for frequent cases of interactive input. Be warned, however, that the advantage of global extension also carries the danger of conflicts, unexpected side–effects, and slow dispatching.


Next: , Previous: , Up: loops generators   [Index]