Bind:   One construct to bind them all
1 Bind:   A Local binding construct
1.1 Informal Introduction
1.2 Bind
bind
def
1.3 Binding Clause Transformers
:  delay
:  object
:  vector
:  string
:  same
:  match
:  match
:  complex
:  vector/  idx
1.4 Defining binding clause transformers
define-binding-clause-transformer
1.5 Assignment transformers
make-transformer
7.5

Bind: One construct to bind them all

Jens Axel Søgaard <jensaxel@soegaard.net>

    1 Bind: A Local binding construct

 (require bind) package: bind

1 Bind: A Local binding construct

1.1 Informal Introduction

This manual documents the binding construct bind and various binding clause transformers. What makes bind interesting is not the support of multiple values, but rather that each binding clause can introduce multiple bindings both value and syntax bindings.

The purpose of let-values and let-syntax is to setup a scope in which to evaluate an expression. For some expressions both are needed:
(let-values (clause ...)
  (let-syntax (clause ...)
    expr))

Let us consider the example, where we have a delayed expression:
(let ([x (delay 1)])
  (+ (force x) (force x)))
It is a bit cumbersome to write (force x) each time the value of the delayed expression is needed. Therefore one can introduce a little syntax to makes things easier:
(let ([x (delay 1)])
  (let-syntax ([clause ...])
    (+ x x)))
where clause makes x expand to (force x). Using bind one can define a binding clause transformer :delay and then simply write:
(bind ([x :delay 1]
  (+ x x)))
During expansion of bind the binding clause transformer :delay will be called with the clause #'[x :delay 1] as input. It will return two lists: a list of let-values clauses and a list of let-syntax clauses. The expander will then splice these clauses together into:
(let ([x (delay 1)])
  (let-syntax ([clause ...])
    (+ x x)))

To summarize: bind can be used as a dropin replacement of let and let-values. Using binding clause transformers a single binding clause can expand into multiple let-values and let-syntax clauses. A few examples are in order before the more formal specification:

Examples:
> (define fish%
    (class object% (init size)
      (super-new)
      (define current-size size)
      (define/public (get-size) current-size)
      (define/public (grow amt) (set! current-size (+ amt current-size)))))
> (bind ([charlie :object (new fish% [size 10])])
    (charlie grow 5)
    (charlie get-size))

15

Example:
> (bind ([v :vector (for/vector ([i 5]) (random 10))])
    (displayln (~a "The vector " v " contains " v))
    (displayln (~a "The first element is: " (v 0)))
    (v! 0 42)
    (displayln (~a "The first element is now: " (v 0)))
    (displayln (~a "The middle three elements are: " (v 1 4))))

The vector #(7 3 0 0 2) contains #(7 3 0 0 2)

The first element is: 7

The first element is now: 42

The middle three elements are: #(3 0 0)

1.2 Bind

syntax

(bind (clause ...) body ...+)

 
clause = [id expr]
  | [(id ...) expr]
  | [id bct expr]
  | [(id ...) bct expr]

Evaluates body .... The clauses introduce bindings and syntax bindings whose scope includes body.

In the simple case, each clause has the form [id expr]. In this simple case bind is equivalent to let.

In the general case each binding clause has an associated binding clause transformer. A binding clause transformer is a function that receives a clause represented as a syntax object and returns two values: a list of let-values binding clauses and a list of let-syntax binding clauses. The bindings for all clauses are then spliced into the following:

(let-values ([(id ...) e] ...)
  (let-syntax ([(id ...) e] ...)
    body ...))

The binding clauses with an explicit binding clause transformer (bct) are [id bct expr] and [(id ...) bct expr]. Here bct must be bound to a binding clause transformer otherwise an error is signaled.

The binding clauses without an explicit binding clause transformer, namely [id expr] and [(id ...) expr] will be expanded by the default binding clause transformer #%bind-clause.

This manual describes the builtin binding clause transformer, but see define-binding-clause-transformer on how to define your own.

syntax

(def id e)

(def (id ...) e)
(def id bct e ...+)
(def (id ...) bct e ...+)

Note: def is not meant to work as a dropin for define.

In the simple cases (def id e) the expression e is evaluated and the result is bound to id.

In the case (def (id ...) e) the expression e is evaluated. It must return the same number of values as there are identifiers id .... Each value is bound to an identifier.

In the general case (def id bct e ...) the binding clause transformer bct is called with the binding clause [id bct e ...] as input. The resulting let-values clauses and let-syntax clauses are spliced into a define-values and define-syntax definitions.
(define-values (id ...) e) ...
(define-syntax (id ...) e) ...

Examples:
> (def v :vector #(3 4))
> (v 0)

3

1.3 Binding Clause Transformers

bct

[id :delay e]

The binding clause transformer :delay binds id to the result of (delay e). Furthermore in body of the bind expression, the following bindings are in place: References to id expand to (force id). Applictions (id arg ...) expand to ((force id) arg ...).

Examples:
> (bind ([x :delay (/ 1 0)]) 3)

3

> (bind ([x :delay 3]) (+ x x))

6

> (bind ([x :delay (/ 1 0)]) (set! x 4) (+ x x))

8

bct

[id :object e]

The binding clause transformer :object binds id to the result of the expression (delay e). In the body of bind applications of the form

(id arg ...)

will expand to

(send id arg ...)

Examples:
> (define fish%
    (class object% (init size)
      (super-new)
      (define current-size size)
      (define/public (get-size) current-size)
      (define/public (grow amt) (set! current-size (+ amt current-size)))))
> (bind ([charlie :object (new fish% [size 10])])
    (charlie grow 5)
    (charlie get-size))

15

bct

[id :vector e]

The binding clause

[id :vector e]

will evaluate the expression e and bind the result to id. In the body of the bind expression:

(v i) expands to (vector-ref v i)

(v i j) expands to (vector-copy v i j)

(v! i x) expands to (vector-set! v i x)

v! expands to (λ (i x) (vector-set! v i x))

Example:
> (bind ([v :vector (vector 3 4)])
        (v! 1 5)
        (+ (v 0) (v 1)))

8

bct

[id :string e]

The binding clause

[id :string e]

will evaluate the expression e and bind the result to id. In the body of the bind expression:

(id i) expands to (string-ref id i)

(id i j) expands to (substring id i j)

(id! i x) expands to (string-set! id i x)

id! expands to (λ (i x) (string-set! id i x))

Example:
> (bind ([s :string "foobar"])
    (~a (s 3 6) (s 0) (s 1) (s 2)))

"barfoo"

bct

[(id ...) :same e]

The binding clause

[(id ...) :same e]

will evaluate the expression eand bind the result value to the identifiers #racket[_id ...].

Example:
> (bind ([(x y) :same 1]) (list x y))

'(1 1)

bct

[id       :match pat e]

bct

[(id ...) :match pat e]

The binding clause

[id :match pat e]

will match the pattern pat against the result of the expression e. The identifier id must appear in the pattern.

The binding clause

[(id ...) :match pat e]

will match the pattern pat against the result of the expression e. All identifiers id ... must appear in the pattern.

Examples:
> (bind ([x :match (list x _) '(2 3)]) x)

2

> (bind ([(x y) :match (list x y) '(2 3)]) (+ x y))

5

bct

[(x y) :complex e]

The binding clause

[(x y) :complex e]

will evaluate the expression e and bind the real part to x and the imaginary part to y in the body of the bind expression.

Example:
> (bind ([(x y) :complex (sqrt -4)]) (list x y))

'(0 2)

bct

[id :vector/idx n e]

The binding clause

[id :vector/idx n e]

will evaluate the expression e and bind the result to id. Furthermore the n identifiers d0 ,id1 ,... will in the body of bind expand to (vector-ref id i). Also (set! idi e) will expand to (vector-set! v i e).

Example:
> (bind ([v :vector/idx 2 (vector 3 4)])
        (set! v1 5)
        (+ v0 v1))

8

1.4 Defining binding clause transformers

syntax

(define-binding-clause-transformer id expr)

(define-binding-clause-transformer (id arg) expr)

Use define-binding-clause-transformer to define binding clause transformers. The form define-binding-clause-transformer works as define-syntax except it binds a binding clause transformer and not a syntax transformer.

The input of a binding clause transformer is a syntax object representing the binding clause. The output is two values: the first is a list of let-values binding clauses, the second is a list of let-syntax binding clauses.

Let us consider an example:

Euclid is writting a library for plane geometry and given a vector v, he wants to use v0 and v1 to refer to the entries of the vector. His solution is to define a binding clause transformer vec2.

Examples:
> (require (for-syntax syntax/parse racket/syntax))
> (define-binding-clause-transformer (:vec2 stx)
    (syntax-parse stx
      [(id:id _ expr)
       (with-syntax ([id0 (format-id #'id "~a0" #'id)]
                     [id1 (format-id #'id "~a1" #'id)])
         (values
  
          (list #'[(id id0 id1)
                   (let ([v expr])
                     (values v
                             (vector-ref v 0)
                             (vector-ref v 1)))])
  
          (list)))]))
> (bind ([v :vec2 #(3 4)])
        (displayln
         (~a "The vector " v " has entries " v0 " and " v1)))

The vector #(3 4) has entries 3 and 4

1.5 Assignment transformers

syntax

(make-transformer
 #:context id
 #:literals (id ...)
 [pat expr] ...)

The standard construct make-set!-transformer used to create assignment transformers is a little cumbersome to use. The form make-transformer is intended to do the same job, with a slightly easier syntax.

Let us see how it is used to define the :delay binding clause transformer.

(define-binding-clause-transformer (:delay stx)
  (syntax-parse stx
    [(id:id _ expr)
     (values
      (list #'[(id1) (delay expr)])
      (list
       #`[id (make-transformer
              #:context so
              #:literals (set!)
              [(set! i e)   (syntax/loc so (set! id1 e))]
              [(i . more)   (syntax/loc so (#%app (force id1) . more))]
              [i:identifier (syntax/loc so (force id1))])]))]))