WhizzML Reference Manual

2.7 Binding constructs (let)

In order to define variables at local scope in whizzml, one uses let expressions, which have the following form:

(let (<id1> <val1>
      <id2> <val2>
      ...
      <idn> <valn>)
  <body>)

All left-hand expressions <id1><idn> must be identifiers. Value expressions <val1><valn> are evaluated in turn and their value bound to the corresponding identifier, which is then usable as a variable in subsequent value expressions and in <body> .

WhizzML is a statically scoped language. When evaluating <valj> , <id1><idj-1> are visible (i.e., in scope ), but not <idj> , and in subsequent value expressions, the computed value for <idj> will shadow any variable of the same name in an outer scope.

(define x "a string")

(let (x 42)
  (+ x 3))  ;; => 45

(let (x 0
      x (+ x 1)
      y (+ x 2))
  (+ x y))  ;; => 4

x ;; => "a string"

2.7.1 Binding list destructuring with let

When an expression evaluates to a list, it is common to access its individual elements afterwards. That is easily accomplished using list accessors (see subsection 4.7.2 ), but let bindings support special syntax for sequential bind destructuring, which allows concise assignment of list elements to named variables as follows.

Instead of using a single identifier in a let binding to specify a variable, one can use a literal list of identifiers. The right hand value must then be a list with, at least, as many values as there are identifiers. The form then binds each identifier to successive elements in the list value. For instance:

(let ([x y] [0 1])
  [y x])   ;; => [1 0]
(let ([x y] [0 1 2 3])
  [y x])   ;; => [1 0]
(let ([x] (range 3 4)
      [y z] [x (* 2 x)])
  [x y z])   ;; => [3 3 6]

More formally, a binding of the form:

(let ([<id0> ... <idn>] <v>
      ...)
 ...)

where <id0><idn> are variable names and <v> is an arbitrary expression that evaluates to a list, is equivalent to the list of bindings:

(let (v <v>
      <id0> (nth v 0)
      <id1> (nth v 1)
      ...
      <idn> (nth v n)
      ...)
  ...)

with v an identifier that does not occur free in the expression <v> .

The dot notation used to denote rest-arguments in procedure parameter declarations (see section 2.4 ) is available in sequence destructure to bind the tail of the list to a name. For instance:

(let ([x y . z] [0 1 2 3 4])
  z)   ;; => [2 3 4]
(let ([x y . z] [0 1])
  z)   ;; => []
(let ([. r] (range 3))
  r)   ;; => [0 1 2]

That is, a binding of the form:

(let ([<id0> ... <idn> . <y>] <v>
      ...)
  ...)

where <id1><idn> and <y> are variable names and <v> is an arbitrary expression that evaluates to a list, is equivalent to the list of bindings:

(let (v <v>
      <id0> (nth v 0)
      <id1> (nth v 1)
      ...
      <idn> (nth v n)
      <y> (drop n v)
      ...)
   ...)

where drop is a primitive that discards the first n elements of a list, v an identifier that does not occur free in the expression <v> .