Choose a conditional
Use if for one yes/no question, cond for several conditions, and case to compare one value with fixed data. This page shows which expressions each form evaluates.
Forms such as cond and case that run by expanding into other forms are called derived forms.
See it run
(case 2 ((1) 'one) ((2) 'two) (else 'other))Observed result
twocase evaluates 2 once and selects the second clause. It returns the symbol two without evaluating the bodies of the other clauses.
(if #t 42 (/ 1 0))
(cond ((< 3 0) (/ 1 0))
((< 3 5) 'small)
(else 'large))Observed result
42
smallif evaluates its test, then only the selected branch. cond tests conditions in order and runs the body of the first true clause. It skips later clauses, including their tests. Neither division runs in this example.
Run each block in the playground. The lines above show the resulting values.
Read the forms
A form is a piece of program syntax, such as (if #t 1 0). A datum is one item of data, such as a number, symbol, or list. In case, the data in each clause are compared with the key using eqv?. They are not evaluated as expressions.
Only #f is false. Every other value counts as true.
| Form | Use it when | Result behavior |
|---|---|---|
if | one test chooses between two branches | returns the values from the selected branch |
cond | several conditions describe different cases | returns the values from the first matching clause |
case | one value is compared with fixed data | evaluates the key once and runs the matching clause |
and / or | later tests depend on earlier results | stops at the first false / true value and returns an operand value |
when / unless | a body should run in only one case | returns zero values when the body is skipped |
and and or stop as soon as the result is known. This is called short-circuit evaluation. They return an operand value without converting it to a boolean.
Run a body conditionally
when runs its body in order when the test is true. unless runs its body when the test is false. Each returns the result of the last body expression.
(when #t 42) returns 42, and (unless #f 7) returns 7. When the body is skipped, the form returns zero values. That happens with when on #f and with unless on a true value.
To handle a catchable error, use (guard (variable clause ...) body ...). guard binds the caught object to variable and tries the clauses in order, as cond does. A final else clause handles any remaining case. If no clause matches, guard raises the original error again.
Check the syntax before running
ifrequires a test and both branches.(if #t 1)raises E130 because the false branch is missing.- If no clause matches in
condorcaseand there is noelse, the result is zero values. Using that result where one value is required raises E320. elsehas a special meaning at the head of acond,case, orguardclause. It is not reserved, so(define else 5)is accepted.condrejects an arrow clause using=>with E130. In acaseclause body,=>is an ordinary name. Evaluating that name without a binding raises E300.unquoteandunquote-splicingoutside quasiquote cause static errors, detected before execution.
Skipping a branch skips its evaluation. Lispex still checks the syntax of the whole program before running it.
How shorthand runs
Lispex rewrites shorthand into core forms before execution. This step is called normalization. The rules for cond, case, and, or, when, unless, let*, named let, do, and quasiquote are built in.
Normalization preserves evaluation order and tail positions. An expression is in tail position when its result is returned directly, with no further work in that call. Generated names cannot collide with your names, and your bindings cannot replace the hidden built-in operations.
Lispex uses these built-in rules for expansion. It rejects define-syntax and user-defined macros.
Keep going
Practice choosing conditions in Data and decisions. See how the shorthand is rewritten in Syntax That Expands into Basic Forms.