Control Flow
This part covers how to control the flow of execution in Dovetail programs.
4.1 If Expressions
Section titled “4.1 If Expressions”In Dovetail, if is an expression - it always returns a value. This makes it more powerful than traditional if statements.
Basic Syntax
Section titled “Basic Syntax”if condition then expression1else expression2Examples
Section titled “Examples”Simple if/else:
let max = if a > b then a else bMulti-line blocks:
let result = if user.isActive then debug("User is active") user.score * 2else debug("User is inactive") 0Single-line form:
let sign = if x > 0 then "positive" else "non-positive"Else If Chains
Section titled “Else If Chains”Use else if for multiple conditions:
let grade = if score >= 90 then "A"else if score >= 80 then "B"else if score >= 70 then "C"else if score >= 60 then "D"else "F"If Without Else
Section titled “If Without Else”When you don’t need a value, you can omit else. The expression returns Unit:
if shouldLog then debug("Logging enabled")Nested If
Section titled “Nested If”If expressions can be nested:
let category = if age < 13 then "child"else if age < 20 then "teenager" else "adult"But prefer else if chains or match for better readability.
4.2 Match Expressions
Section titled “4.2 Match Expressions”match is Dovetail’s powerful pattern matching construct. It’s like switch in other languages, but much more expressive.
Basic Syntax
Section titled “Basic Syntax”match value with case pattern1 => result1 case pattern2 => result2 case _ => defaultResultMatching Literals
Section titled “Matching Literals”let dayName = match dayNumber with case 1 => "Monday" case 2 => "Tuesday" case 3 => "Wednesday" case 4 => "Thursday" case 5 => "Friday" case 6 => "Saturday" case 7 => "Sunday" case _ => "Invalid day"Matching Enums
Section titled “Matching Enums”Pattern matching shines with enums:
enum Color = Red Green Blue RGB(Uint8, Uint8, Uint8)
let description = match color with case Red => "Pure red" case Green => "Pure green" case Blue => "Pure blue" case RGB(r, g, b) => "RGB($r, $g, $b)"Matching Option
Section titled “Matching Option”let message = match maybeUser with case Some(user) => "Hello, ${user.name}!" case None => "Hello, guest!"Matching Result
Section titled “Matching Result”match fetchData() with case Ok(data) => processData(data) case Error(err) => debug("Error: $err")Destructuring Records
Section titled “Destructuring Records”record Point = x: Int32 y: Int32
let quadrant = match point with case Point { x; y } if x > 0 and y > 0 => "Q1" case Point { x; y } if x < 0 and y > 0 => "Q2" case Point { x; y } if x < 0 and y < 0 => "Q3" case Point { x; y } if x > 0 and y < 0 => "Q4" case _ => "Origin or on axis"Commas are also accepted between record-pattern fields, but dovetail fmt
normalizes them to semicolons. In a multiline pattern, semicolons appear
between fields but not after the last field.
Matching Tuples
Section titled “Matching Tuples”let description = match (x, y) with case (0, 0) => "Origin" case (0, _) => "On Y axis" case (_, 0) => "On X axis" case (a, b) if a == b => "On diagonal" case _ => "Somewhere else"Matching Lists
Section titled “Matching Lists”A list matches either as a fixed set of elements or as a head and a tail:
let description = match xs with case [] => "empty" case [only] => "exactly one" case [a, b] => "exactly two" case h :: t => "at least one more"h :: t binds h to the first element and t to the rest, which makes
recursion over a list read directly:
function sum(xs: List<Int32>): Int32 = match xs with case [] => 0 case h :: t => h + sum(t)[] and h :: t together cover every list, so the pair needs no wildcard:
let first = match xs with case [] => "none" case h :: t => h.format()Fixed-length patterns do not cover everything, though — case [] plus
case [a, b] leaves a one-element list unmatched, and the compiler says so:
non-exhaustive match: missing case for [_]These patterns match a List only. Arrays have no pattern form; index them or
iterate them instead.
Guard Conditions
Section titled “Guard Conditions”Add if after a pattern for additional conditions:
let category = match age with case n if n < 0 => "Invalid" case n if n < 13 => "Child" case n if n < 20 => "Teenager" case n if n < 65 => "Adult" case _ => "Senior"The Wildcard Pattern
Section titled “The Wildcard Pattern”Use _ to match anything you don’t care about:
match triple with case (first, _, _) => first // Only care about first elementVariable Binding
Section titled “Variable Binding”Patterns can bind values to names:
match result with case Ok(value) => debug("Got: $value") case Error(e) => debug("Error: $e")Exhaustiveness
Section titled “Exhaustiveness”The compiler ensures all cases are covered. This won’t compile:
enum Direction = North | South | East | West
// Error: non-exhaustive matchlet name = match dir with case North => "N" case South => "S" // Missing East and West!Use _ as a catch-all if needed:
let name = match dir with case North => "N" case South => "S" case _ => "Other"4.3 Loops
Section titled “4.3 Loops”Dovetail provides for and while loops for iteration.
For Loops
Section titled “For Loops”Iterate over a value that implements Iterable<T>, including arrays, lists, slices, and collection ranges:
for item in list do debug(item)Over a slice:
let values = [|10, 20, 30, 40|]let mutable total = 0for value in values[|1..3|] do total = total + valueassert total == 50See Lists, Arrays, and Slices for slicing forms and shared-mutation behavior. The .. notation is only valid inside slicing brackets; use the collection Range API for a numeric loop.
With index using enumerate:
for (index, item) in list.zipWithIndex() do debug("$index: $item")Over a range:
for i in Range.new(0, 10) do debug(i) // 0 to 9
for i in Range.inclusive(0, 10) do debug(i) // 0 to 10 (inclusive)Iterating over maps:
for (key, value) in map do debug("$key -> $value")While Loops
Section titled “While Loops”Execute while a condition is true:
let mutable count = 0while count < 10 do debug(count) count = count + 1Loop as Expression
Section titled “Loop as Expression”Loops return Unit:
let result: Unit = for i in [|0, 1, 2, 3, 4|] do debug(i)Early Exit
Section titled “Early Exit”Use break to exit a loop early:
let mutable found = falsefor item in list do if item == target then found = true breakUse continue to skip to the next iteration:
for item in list do if item < 0 then continue // Skip negative numbers debug(item)Functional Alternatives
Section titled “Functional Alternatives”For many use cases, functional methods are cleaner than loops:
// Instead of a loop to transformlet doubled = numbers.map(x => x * 2)
// Instead of a loop to filterlet positive = numbers.filter(x => x > 0)
// Instead of a loop to sumlet total = numbers.fold(0, (acc, x) => acc + x)
// Instead of a loop to findlet found = numbers.find(x => x > 100)These functional approaches are often more readable and eliminate the need for mutable variables.
Summary
Section titled “Summary”Dovetail provides expressive control flow constructs:
- If expressions - Always return a value, can be used anywhere an expression is expected
- Match expressions - Powerful pattern matching with destructuring, guards, and exhaustiveness checking
- Loops -
forandwhilefor iteration, withbreakandcontinuefor control
A key principle: prefer pattern matching over long if/else chains, and prefer functional methods (map, filter, fold) over imperative loops when it makes the code clearer.
In the next part, we’ll explore functions in depth.
For restrictions on loops that suspend, see Async Programming.
