Conditionals

Kawa Scheme has the usual conditional expression forms, such as if, case, and, and or:

(if (> 3 2) 'yes 'no)          ⇒ yes

Kawa also allows you bind variables in the condition, using the ‘?’ operator.

(if (and (? x ::integer (get-value)) (> x 0))
  (* x 10)
  'invalid)

In the above, if (get-value) evaluates to an integer, that integer is bound to the variable x, which is visible in both following sub-expression of and, as well case the true-part of the if.

Specifically, the first sub-expression of an if is a test-or-match, which can be a test-expression, or a ‘?’ match expression, or a combination using and:

test-or-match ::= test-expression
  | (? pattern expression )
  | (and test-or-match*)

A test-or-match is true if every nested test-expression is true, and every ‘?’ operation succeeds. It produces a set of variable bindings which is the union of the bindings produced by all the patterns. In an and form, bindings produced by a pattern are visible to all subsequent test-or-match sub-expressions.

Syntax: ? pattern expression

The form (? P V) informally is true if the value of V matches the pattern P. Any variables bound in P are in scope in the “true” path of the containing conditional.

This has the form of an expression, but it can only be used in places where a test-or-match is required. For example it can be used as the first clause of an if expression, in which case the scope of the variables bound in the pattern includes the second (consequent) sub-expression. On the other hand, a ‘?’ form may not be used as an argument to a procedure application.

Syntax: if test-or-match consequent alternate

Syntax: if test-or-match consequent

consequent ::= expression
alternate ::= expression

An if expression is evaluated as follows: first, the test-or-match is evaluated. If it it true, then consequent is evaluated and its values are returned. Otherwise alternate is evaluated and its values are returned. If test yields #f and no alternate is specified, then the result of the expression is void.

(if (> 2 3) 'yes 'no)          ⇒ no
(if (> 3 2)
    (- 3 2)
    (+ 3 2))                   ⇒ 1
(if #f #f)                     ⇒ #!void
(if (? x::integer 3)
  (+ x 1)
  'invalid)                    ⇒ 4
(if (? x::integer 3.4)
  (+ x 1)
  'invalid)                    ⇒ 'invalid

The consequent and alternate expressions are in tail context if the if expression itself is.

Syntax: cond cond-clause+

Syntax: cond cond-clause* (else expression)

cond-clause ::= (test-or-match body)
    | (test => expression)

A cond expression is evaluated by evaluating the test-or-matchs of successive cond-clauses in order until one of them evaluates to a true value. When a test-or-match is true value, then the remaining expressions in its cond-clause are evaluated in order, and the results of the last expression in the cond-clause are returned as the results of the entire cond expression. Variables bound by the test-or-match are visible in body. If the selected cond-clause contains only the test-or-match and no expressions, then the value of the last test-expression is returned as the result. If the selected cond-clause uses the => alternate form, then the expression is evaluated. Its value must be a procedure. This procedure should accept one argument; it is called on the value of the test-expression and the values returned by this procedure are returned by the cond expression.

If all test-or-matchs evaluate to #f, and there is no else clause, then the conditional expression returns unspecified values; if there is an else clause, then its expressions are evaluated, 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 ('(1 2 3) => cadr)
      (else #f))               ⇒ 2

For a cond-clause of one of the following forms:

(test expression*)
(else expression expression*)

the last expression is in tail context if the cond form itself is. For a cond clause of the form:

(test => expression)

the (implied) call to the procedure that results from the evaluation of expression is in tail context if the cond form itself is.

Syntax: case case-key case-clause+

Syntax: case case-key case-clause* case-else-clause

case-key ::= expression
case-clause ::= ((datum*) expression+)
    | ((datum*) => expression)
case-else-clause ::= (else  expression+)
    | (else => expression)

Each datum is an external representation of some object. Each datum in the entire case expression should be distinct.

A case expression is evaluated as follows.

  1. The case-key is evaluated and its result is compared using eqv? against the data represented by the datums of each case-clause in turn, proceeding in order from left to right through the set of clauses.

  2. If the result of evaluating case-key is equivalent to a datum of a case-clause, the corresponding expressions are evaluated from left to right and the results of the last expression in the case-clause are returned as the results of the case expression. Otherwise, the comparison process continues.

  3. If the result of evaluating key is different from every datum in each set, then if there is an case-else-clause its expressions are evaluated and the results of the last are the results of the case expression; otherwise the result of case expression is unspecified.

If the selected case-clause or case-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))                    ⇒ unspecified
(case (car '(c d))
  ((a e i o u) 'vowel)
  ((w y) 'semivowel)
  (else => (lambda (x) x)))    ⇒ c

The last expression of a case clause is in tail context if the case expression itself is.

Syntax: match match-key match-clause+

The match form is a generalization of case using patterns,

match-key ::= expression
match-clause ::=
  ( pattern [guardbody )

The match-key is evaluated, Then the match-clauses are tried in order. The first match-clause whose pattern matches (and the guard, if any, is true), is selected, and the corresponding body evaluated. It is an error if no match-clause matches.

(match value
  (0 (found-zero))
  (x #!if (> x 0) (found-positive x))
  (x #!if (< x 0) (found-negative x))
  (x::symbol (found-symbol x))
  (_ (found-other)))

One case feature is not (yet) directly supported by match: Matching against a list of values. However, this is easy to simulate using a guard using memq, memv, or member:

;; compare similar example under case
(match (car '(c d))
  (x #!if (memv x '(a e i o u)) ’vowel)
  (x #!if (memv x '(w y)) ’semivowel)
  (x x))

Syntax: and test-or-match*

If there are no test-or-match forms, #t is returned.

If the and is not in test-or-match context, then the last sub-expression (if any) must be a test-expression, and not a ‘?’ form. In this case the test-or-match expressions are evaluated from left to right until either one of them is false (a test-expression is false or a ‘?’ match fails), or the last test-expression is reached. In the former case, the and expression returns #f without evaluating the remaining expressions. In the latter case, the last expression is evaluated and its values are returned.

If the and is in test-or-match context, then the last sub-form can be ‘?’ form. They are evaluated in order: If one of them is false, the entire and is false; otherwise the and is true.

Regardless, any bindings made by earlier ‘?’ forms are visible in later test-or-match forms.

(and (= 2 2) (> 2 1))          ⇒  #t
(and (= 2 2) (< 2 1))          ⇒  #f
(and 1 2 'c '(f g))            ⇒  (f g)
(and)                          ⇒  #t
(and (? x ::int 23) (> x 0))   ⇒  #t

The and keyword could be defined in terms of if using syntax-rules as follows:

(define-syntax and
  (syntax-rules ()
    ((and) #t)
    ((and test) test)
    ((and test1 test2 ...)
     (if test1 (and test2 ...) #t))))

The last test-expression is in tail context if the and expression itself is.

Syntax: or test-expression

If there are no test-expressions, #f is returned. Otherwise, the test-expressions are evaluated from left to right until a test-expression returns a true value val or the last test-expression is reached. In the former case, the or expression returns val without evaluating the remaining expressions. In the latter case, the last expression is evaluated and its values are returned.

(or (= 2 2) (> 2 1))           ⇒ #t
(or (= 2 2) (< 2 1))           ⇒ #t
(or #f #f #f)                  ⇒ #f
(or '(b c) (/ 3 0))            ⇒ (b c)

The or keyword could be defined in terms of if using syntax-rules as follows:

(define-syntax or
  (syntax-rules ()
    ((or) #f)
    ((or test) test)
    ((or test1 test2 ...)
     (let ((x test1))
       (if x x (or test2 ...))))))

The last test-expression is in tail context if the or expression itself is.

Procedure: not test-expression

The not procedure returns #t if test-expression is false, and returns #f otherwise.

(not #t)         ⇒  #f
(not 3)          ⇒  #f
(not (list 3))   ⇒  #f
(not #f)         ⇒  #t
(not ’())        ⇒  #f
(not (list))     ⇒  #f
(not ’nil)       ⇒  #f
(not #!null)     ⇒  #t

Syntax: when test-expression form...

If test-expression is true, evaluate each form in order, returning the value of the last one.

Syntax: unless test-expression form...

If test-expression is false, evaluate each form in order, returning the value of the last one.