Skip to content

LispKit Control

Matthias Zenger edited this page Dec 11, 2021 · 2 revisions

Sequencing

(begin expr ... exprn) [syntax]

begin evaluates expr, ..., exprn sequentially from left to right. The values of the last expression exprn are returned. This special form is typically used to sequence side effects such as assignments or input and output.

Conditionals

(if test consequent) [syntax]
(if test consequent alternate)

An if expression is evaluated as follows: first, expression test is evaluated. If it yields a true value, then expression consequent is evaluated and its values are returned. Otherwise, alternate is evaluated and its values are returned. If expression test yields a false value and no alternate expression is specified, then the result of the expression is void.

(if (> 3 2) 'yes 'no)         ⇒ yes
(if (> 2 3) 'yes 'no)         ⇒ yes
(if (> 3 2) (- 3 2) (+ 3 2))  ⇒ 1

(when test consequent ...) [syntax]

The test expression is evaluated, and if it evaluates to a true value, the expressions consequent ... are evaluated in order. The result of the when expression is the value to which the last consequent expression evaluates or void if test evaluates to false.

(when (= 1 1.0)
  (display "1")
  (display "2"))  ⇒ (void), prints: 12

(unless test alternate ...) [syntax]

The test is evaluated, and if it evaluates to false, the expressions alternate ... are evaluated in order. The result of the unless expression is the value to which the last consequent expression evaluates or void if test evaluates to a true value.

(unless (= 1 1.0)
  (display "1")
  (display "2"))  ⇒ (void), prints nothing

(cond clause1 clause2 ...) [syntax]

Clauses like clause1 and clause2 take one of two forms, either

  • (_test expr1 ..._), or
  • (_test_ => _expr_)

The last clause in a cond expression can be an "else clause", which has the form

  • (else _expr1 expr2 ..._)

A cond expression is evaluated by evaluating the test expressions of successive clauses in order until one of them evaluates to a true value. When a test expression evaluates to a true value, the remaining expressions in its clause are evaluated in order, and the results of the last expression are returned as the results of the entire cond expression.

If the selected clause contains only the test and no expressions, then the value of the test expression is returned as the result of the cond expression. If the selected clause uses the => alternate form, then the expression is evaluated. It is an error if its value is not a procedure that accepts one argument. This procedure is then called on the value of the test and the values returned by this procedure are returned by the cond expression.

If all tests evaluate to #f, and there is no else clause, then the result of the conditional expression is void. If there is an else clause, then its expressions are evaluated in order, and the values of the last one are returned.

(cond ((> 3 2) ’greater)
      ((< 3 2) ’less))     ⇒ greater

(cond ((> 3 3) ’greater)
      ((< 3 3) ’less)
      (else ’equal))       ⇒ equal

(cond ((assv ’b ’((a 1) (b 2))) => cadr)
      (else #f))           ⇒ 2

(case key clause1 clause2 ...) [syntax]

key can be any expression. Each clause clause1, clause2, ... has the form:

  • ((_datum1 ..._) _expr1 expr2 ..._)

where each datum is an external representation of some object. It is an error if any of the datums are the same anywhere in the expression. Alternatively, a clause can be of the form:

  • ((_datum1 ..._) => _expr_)

The last clause in a case expression can be an "else clause", which has one of the following forms:

  • (else _expr1 expr2 ..._), or
  • (else => _expr_)

A case expression is evaluated as follows. Expression key is evaluated and its result is compared against each datum. If the result of evaluating key is the same, in the sense of eqv?, to a datum, then the expressions in the corresponding clause are evaluated in order and the results of the last expression in the clause are returned as the results of the case expression.

If the result of evaluating key is different from every datum, then if there is an else clause, its expressions are evaluated and the results of the last are the results of the case expression. Otherwise, the result of the case expression is void.

If the selected clause or else clause uses the => alternate form, then the expression is evaluated. It is an error, if its value is not a procedure accepting one argument. This procedure is then called on the value of the key and the values returned by this procedure are returned by the case expression.

(case (* 2 3)
  ((2 3 5 7) ’prime)
  ((1 4 6 8 9) ’composite))   ⇒ composite

(case (car ’(c d))
  ((a) ’a)
  ((b) ’b))                   ⇒ (void)

(case (car ’(c d))
  ((a e i o u) ’vowel)
  ((w y) ’semivowel)
  (else => (lambda (x) x)))   ⇒ c

Local bindings

The binding constructs let, let*, letrec, letrec*, let-values, and let*-values give Scheme a block structure. The syntax of the first four constructs is identical, but they differ in the regions they establish for their variable bindings. In a let expression, the initial values are computed before any of the variables become bound. In a let* expression, the bindings and evaluations are performed sequentially. While in letrec and letrec* expressions, all the bindings are in effect while their initial values are being computed, thus allowing mutually recursive definitions. The let-values and let*-values constructs are analogous to let and let* respectively, but are designed to handle multiple-valued expressions, binding different identifiers to the returned values.

(let bindings body) [syntax]

bindings has the form ((variable init) ...), where each init is an expression, and body is a sequence of zero or more definitions followed by a sequence of one or more expressions. It is an error for variable to appear more than once in the list of variables being bound.

All init expressions are evaluated in the current environment, the variables are bound to fresh locations holding the results, the body is evaluated in the extended environment, and the values of the last expression of body are returned. Each binding of a variable has body as its scope.

(let ((x 2) (y 3))
  (* x y))             ⇒  6

(let ((x 2) (y 3))
  (let ((x 7)
        (z (+ x y)))
    (* z x)))          ⇒ 35

(let* bindings body) [syntax]

bindings has the form ((variable init) ...), where each init is an expression, and body is a sequence of zero or more definitions followed by a sequence of one or more expressions.

The let* binding construct is similar to let, but the bindings are performed sequentially from left to right, and the region of a binding indicated by (variable init) is that part of the let* expression to the right of the binding. Thus, the second binding is done in an environment in which the first binding is visible, and so on. The variables need not be distinct.

(let ((x 2) (y 3))
      (let* ((x 7)
             (z (+ x y)))
        (* z x)))         ⇒  70

(letrec bindings body) [syntax]

bindings has the form ((variable init) ...), where each init is an expression, and body is a sequence of zero or more definitions followed by a sequence of one or more expressions. It is an error for variable to appear more than once in the list of variables being bound.

The variables are bound to fresh locations holding unspecified values, the init expressions are evaluated in the resulting environment, each variable is assigned to the result of the corresponding init expression, the body is evaluated in the resulting environment, and the values of the last expression in body are returned. Each binding of a variable has the entire letrec expression as its scope, making it possible to define mutually recursive procedures.

(letrec ((even? (lambda (n)
                  (if (zero? n) #t (odd? (- n 1)))))
         (odd?  (lambda (n)
                  (if (zero? n) #f (even? (- n 1))))))
  (even? 88))  ⇒  #t

One restriction of letrec is very important: if it is not possible to evaluate each init expression without assigning or referring to the value of any variable, it is an error. The restriction is necessary because letrec is defined in terms of a procedure call where a lambda expression binds the variables to the values of the init expressions. In the most common uses of letrec, all the init expressions are lambda expressions and the restriction is satisfied automatically.

(letrec* bindings body) [syntax]

bindings has the form ((⟨variable init) ...), where each init is an expression, and body is a sequence of zero or more definitions followed by a sequence of one or more expressions. It is an error for variable to appear more than once in the list of variables being bound.

The variables are bound to fresh locations, each variable is assigned in left-to-right order to the result of evaluating the corresponding init expression, the body is evaluated in the resulting environment, and the values of the last expression in body are returned. Despite the left-to-right evaluation and assignment order, each binding of a variable has the entire letrec* expression as its region, making it possible to define mutually recursive procedures. If it is not possible to evaluate each init expression without assigning or referring to the value of the corresponding variable or the variable of any of the bindings that follow it in bindings, it is an error. Another restriction is that it is an error to invoke the continuation of an init expression more than once.

(letrec* ((p (lambda (x)
               (+ 1 (q (- x 1)))))
          (q (lambda (y)
               (if (zero? y) 0 (+ 1 (p (- y 1))))))
          (x (p 5))
          (y x))
  y)  ⇒  5

(let-values bindings body) [syntax]

bindings has the form ((⟨formals init) ...), where each formals is a list of variables, init is an expression, and body is zero or more definitions followed by a sequence of one or more expressions. It is an error for a variable to appear more than once in formals.

The init expressions are evaluated in the current environment as if by invoking call-with-values, and the variables occurring in list formals are bound to fresh locations holding the values returned by the init expressions, where the formals are matched to the return values in the same way that the formals in a lambda expression are matched to the arguments in a procedure call. Then, body is evaluated in the extended environment, and the values of the last expression of body are returned. Each binding of a variable has body as its scope.

It is an error if the variables in list formals do not match the number of values returned by the corresponding init expression.

(let-values (((root rem) (exact-integer-sqrt 32)))
  (* root rem))  ⇒  35

(let*-values bindings body) [syntax]

bindings has the form ((⟨formals init) ...), where each formals is a list of variables, init is an expression, and body is zero or more definitions followed by a sequence of one or more expressions. It is an error for a variable to appear more than once.

The let*-values construct is similar to let-values, but the init expressions are evaluated and bindings created sequentially from left to right, with the region of the bindings of each variable in formals including the init expressions to its right as well as body. Thus the second init expression is evaluated in an environment in which the first set of bindings is visible and initialized, and so on.

(let ((a 'a) (b 'b) (x 'x) (y 'y))
  (let*-values (((a b) (values x y))
                ((x y) (values a b)))
    (list a b x y)))  ⇒  (x y x y)

(letrec-values bindings body) [syntax]

bindings has the form ((⟨formals init) ...), where each formals is a list of variables, init is an expression, and body is zero or more definitions followed by a sequence of one or more expressions. It is an error for a variable to appear more than once.

First, the variables of the formals lists are bound to fresh locations holding unspecified values. Then, the init expressions are evaluated in the current environment as if by invoking call-with-values, where the formals are matched to the return values in the same way that the formals in a lambda expression are matched to the arguments in a procedure call. Finally, body is evaluated in the resulting environment, and the values of the last expression in body are returned. Each binding of a variable has the entire letrec-values expression as its scope, making it possible to define mutually recursive procedures.

(letrec-values
  (((a)   (lambda (n)
            (if (zero? n) #t (odd? (- n 1)))))
   ((b c) (values
            (lambda (n)
              (if (zero? n) #f (even? (- n 1))))
            a)))
  (list (a 1972) (b 1972) (c 1972)))
⇒  (#t #f #t)

(let-optionals args (arg ... (var default) ...) body ...) [syntax]
(let-optionals args (arg ... (var default) ... . rest) body ...)

This binding construct can be used to handle optional arguments of procedures. args refers to the rest parameter list of a procedure or lambda expression. let-optionals binds the variables arg ... to the arguments available in args. It is an error if there are not sufficient elements in args. Then, the variables var, ... are bound to the remaining elements available in list args, or to default, ... if there are not enough elements available in args. Variables are bound in parallel, i.e. all default expressions are evaluated in the current environment in which the new variables are not bound yet. Then, body is evaluated in the extended environment including all variable definitions of let-optionals, and the values of the last expression of body are returned. Each binding of a variable has body as its scope.

(let-optionals '("zero" "one" "two")
               (zero (one 1) (two 2) (three 3))
  (list zero one two three))  ⇒  ("zero" "one" "two" 3)

(let*-optionals args (arg ... (var default) ...) body ...) [syntax]
(let*-optionals args (arg ... (var default) ... . rest) body ...)

The let*-optionals construct is similar to let-optionals, but the default expressions are evaluated and bindings created sequentially from left to right, with the scope of the bindings of each variable including the default expressions to its right as well as body. Thus, the second default expression is evaluated in an environment in which the first binding is visible and initialized, and so on.

(let*-optionals '(0 10 20)
                (zero
                 (one (+ zero 1))
                 (two (+ zero one 1))
                 (three (+ two 1)))
  (list zero one two three))  ⇒  (0 10 20 21)

(let-keywords args (binding ...) body ...) [syntax]

binding has one of two forms:

  • (var default), and
  • (var keyword default)

where var is a variable, keyword is a symbol, and default is an expression. It is an error for a variable var to appear more than once.

This binding construct can be used to handle keyword arguments of procedures. args refers to the rest parameter list of a procedure or lambda expression. let-keywords binds the variables var, ... by name, i.e. by searching in args for the keyword argument. If an optional keyword is provided, it is used as the name of the keyword to search for, otherwise, var is used, appending :. If the keyword is not found in args, var is bound to default.

Variables are bound in parallel, i.e. all default expressions are evaluated in the current environment in which the new variables are not bound yet. Then, body is evaluated in the extended environment including all variable definitions of let-keywords, and the values of the last expression of body are returned. Each binding of a variable has body as its scope.

(define (make-person . args)
  (let-keywords args ((name "J. Doe")
                      (age 0)
                      (occupation job: 'unknown))
    (list name age occupation)))
(make-person)                      ⇒  ("J. Doe" 0 unknown)
(make-person 'name: "M. Zenger")   ⇒  ("M. Zenger" 0 unknown)
(make-person 'age: 31 'job: 'eng)  ⇒  ("J. Doe" 31 eng)

(let*-keywords args (binding ...) body ...) [syntax]

binding has one of two forms:

  • (var default), and
  • (var keyword default)

where var is a variable, keyword is a symbol, and default is an expression. It is an error for a variable var to appear more than once.

The let*-keywords construct is similar to let-keywords, but the default expressions are evaluated and bindings created sequentially from left to right, with the scope of the bindings of each variable including the default expressions to its right as well as body. Thus the second default expression is evaluated in an environment in which the first binding is visible and initialized, and so on.

Local syntax bindings

The let-syntax and letrec-syntax binding constructs are analogous to let and letrec, but they bind syntactic keywords to macro transformers instead of binding variables to locations that contain values. Syntactic keywords can also be bound globally or locally with define-syntax.

(let-syntax bindings body) [syntax]

bindings has the form ((keyword transformer) ...). Each keyword is an identifier, each transformer is an instance of syntax-rules, and body is a sequence of one or more definitions followed by one or more expressions. It is an error for a keyword to appear more than once in the list of keywords being bound.

body is expanded in the syntactic environment obtained by extending the syntactic environment of the let-syntax expression with macros whose keywords are the keyword symbols bound to the specified transformers. Each binding of a keyword has body as its scope.

(let-syntax
  ((given-that (syntax-rules ()
                 ((_ test stmt1 stmt2 ...)
                   (if test
                       (begin stmt1 stmt2 ...))))))
  (let ((if #t))
    (given-that if (set! if ’now))
    if))       ⇒  now

(let ((x ’outer))
  (let-syntax ((m (syntax-rules () ((m) x))))
    (let ((x ’inner))
      (m))))   ⇒  outer

(letrec-syntax bindings body) [syntax]

bindings has the form ((keyword transformer) ...). Each keyword is an identifier, each transformer is an instance of syntax-rules, and body is a sequence of one or more definitions followed by one or more expressions. It is an error for a keyword to appear more than once in the list of keywords being bound.

body is expanded in the syntactic environment obtained by extending the syntactic environment of the letrec-syntax expression with macros whose keywords are the keywords, bound to the specified transformers. Each binding of a keyword symbol has the transformer as well as the body within its scope, so the transformers can transcribe expressions into uses of the macros introduced by the letrec-syntax expression.

(letrec-syntax
        ((my-or (syntax-rules ()
                  ((my-or) #f)
                  ((my-or e) e)
                  ((my-or e1 e2 ...)
                    (let ((temp e1))
                      (if temp temp (my-or e2 ...)))))))
  (let ((x #f)
        (y 7)
        (temp 8)
        (let odd?)
        (if even?))
    (my-or x
           (let temp)
           (if y)
           y)))       ⇒  7

Iteration

(do ((variable init step) ...) [syntax]
        (test res ...)
    command ...)

A do expression is an iteration construct. It specifies a set of variables to be bound, how they are to be initialized at the start, and how they are to be updated on each iteration. When a termination condition test is met (i.e. it evaluates to #t), the loop exits after evaluating the res expressions.

A do expression is evaluated as follows: The init expressions are evaluated, the variables are bound to fresh locations, the results of the init expressions are stored in the bindings of the variables, and then the iteration phase begins.

Each iteration begins by evaluating test. If the result is false, then the command expressions are evaluated in order, the step expressions are evaluated in some unspecified order, the variables are bound to fresh locations, the results of the step expressions are stored in the bindings of the variables, and the next iteration begins.

If test evaluates to #t, then the res expressions are evaluated from left to right and the values of the last res expression are returned. If no res expressions are present, then the do expression evaluates to void.

The scope of the binding of a variable consists of the entire do expression except for the init expressions. It is an error for a variable to appear more than once in the list of do variables. A step can be omitted, in which case the effect is the same as if (variable init variable) had been written instead of (variable init).

(do ((vec (make-vector 5))
         (i 0 (+ i 1)))
        ((= i 5) vec)
      (vector-set! vec i i))  ⇒  #(0 1 2 3 4)

(let ((x '(1 3 5 7 9)))
      (do ((x x (cdr x))
           (sum 0 (+ sum (car x))))
          ((null? x) sum)))   ⇒  25
Clone this wiki locally