Control Flow in Dylan
Learn conditionals, case and select statements, loops, and non-local exits in Dylan with practical Docker-ready examples
Control flow is how a program decides what to do and how many times to do it. Dylan gives you a comfortable, ALGOL-flavored toolkit — if/elseif/else, while, until, and for — but it carries a distinctly Lisp sensibility underneath. The most important thing to internalize early is that most of Dylan’s control-flow constructs are expressions: an if doesn’t just branch, it produces a value, so you can bind its result directly to a variable.
As a multi-paradigm language, Dylan blends procedural looping with functional, expression-oriented branching. Where C-family languages have a switch statement, Dylan offers two complementary constructs: case, which evaluates a series of arbitrary boolean tests, and select, which compares a single value against sets of candidates. And one design choice surprises newcomers: Dylan has no break or continue keywords. Instead, it uses non-local exits through the block construct — a more general mechanism that, as you’ll see, can express early exit, iteration skipping, and even resource cleanup.
In this tutorial you’ll branch with if/elseif/else, choose among many possibilities with case and select, iterate with for, while, and until, and learn the idiomatic Dylan way to break out of and skip iterations using block. Remember from earlier tutorials that only #f is false in Dylan — every other value, including 0 and the empty string, is truthy.
Conditionals: if, elseif, and if-as-an-expression
The if construct evaluates a condition and runs one branch. Chain alternatives with elseif (one word in Dylan, not else if), and close the whole form with end if. Because if is an expression, you can also use it inline to produce a value.
Create a file named conditionals.dylan:
| |
Two things distinguish Dylan here. First, elseif is a single keyword. Second, if is genuinely an expression — let label = if (...) ... else ... end works because the if evaluates to whichever branch is taken. There is no separate ternary ?: operator in Dylan because if already fills that role.
Choosing among many: case and select
Dylan splits “multi-way branching” into two constructs. Use case when each branch is governed by its own arbitrary boolean test — it evaluates the tests top to bottom and runs the first one that is true. Use select when you are comparing one value against fixed candidates; it’s closer to a classic switch. Both use => to separate a branch’s test from its body, and both accept an otherwise fall-through clause.
Create a file named selection.dylan:
| |
Notice the by \= clause in the last select. By default select compares branches with == (identity), which is correct for integers and symbols but not for strings, where two equal strings can be distinct objects. Supplying by \= tells select to use the value-equality function = instead — \= is the prefix, function-style spelling of the = operator.
Loops: for, while, and until
Dylan’s for is remarkably flexible. The simplest form counts over a numeric range with from ... to, optionally stepping by an increment. It can also walk a collection with in. For condition-driven repetition, while loops while a test is true, and until loops until a test becomes true.
Create a file named loops.dylan:
| |
Recall from the operators tutorial that assignment uses :=, never = — so countdown := countdown - 1 mutates the local binding while = stays reserved for equality. The for ... in form is the idiomatic way to traverse any Dylan collection (lists, vectors, ranges, even strings), and a single for header can combine several clauses, which makes it the workhorse of Dylan iteration.
Loop control: break and continue with block
Here is where Dylan diverges sharply from C-family languages: there is no break and no continue. Instead, Dylan provides block, which establishes a named non-local exit. The name you give in block (name) is bound to an exit function — calling it immediately unwinds and leaves the block. Wrapping a whole loop gives you “break”; wrapping a single iteration’s body gives you “continue”.
Create a file named loop_control.dylan:
| |
The exit names stop and next are not keywords — they’re ordinary variables bound to exit functions, so you can name them whatever reads best. This single mechanism is more general than break/continue: because the exit function is a first-class value, you could even pass it to another function and trigger the exit from deep inside a call chain. The built-in predicates even? and odd? follow Dylan’s convention of suffixing boolean-returning functions with ?.
Running with Docker
| |
Expected Output
Running conditionals.dylan:
It is mild
Today is comfortable
Mild and humid
Running selection.dylan:
Grade: B
Season: Spring
It is the weekend
Running loops.dylan:
Count: 1
Count: 2
Count: 3
Even: 0
Even: 2
Even: 4
Even: 6
Fruit: apple
Fruit: banana
Fruit: cherry
T-minus 3
T-minus 2
T-minus 1
Step 1
Step 2
Step 3
Running loop_control.dylan:
Visiting 1
Visiting 2
Visiting 3
Visiting 4
Stopping at 5
Odd: 1
Odd: 3
Odd: 5
Guarded odd: 1
Guarded odd: 3
Guarded odd: 5
Key Concepts
ifis an expression — it evaluates to the value of whichever branch runs, so you can writelet x = if (test) a else b end. Dylan needs no separate ternary operator.elseifis one word — chain alternatives withelseif, and close the form withend if. The condition is any expression, and remember only#fis false.casevsselect—caseevaluates a series of independent boolean tests and runs the first true one;selectcompares a single value against candidate sets. Both support anotherwiseclause.selectdefaults to==— that’s correct for integers and symbols, but useselect (value by \=)for strings and other values where you need value equality rather than identity.- Three looping forms —
forhandles numeric ranges (from ... to ... by) and collection traversal (in);whilerepeats while a test is true;untilrepeats until a test becomes true. - No
breakorcontinue— Dylan usesblockto create named non-local exits. Wrap a loop to break out of it; wrap an iteration body to skip it. The exit name is an ordinary first-class function value. - Assignment is
:=— loop counters and accumulators are mutated with:=, keeping=free as the equality predicate.
Running Today
All examples can be run using Docker:
docker pull codearchaeology/dylan:latest
Comments
Loading comments...
Leave a Comment