Control Flow
Keel provides several ways to control program flow. Unlike imperative languages, all control flow constructs in Keel are expressions that return values.
If Expressions
The if-then-else expression evaluates to one of two values:
let x = 5
let result =
if x > 0 then
"positive"
else
"non-positive"
result
Try itBoth branches must have the same type:
let condition = True
-- Valid: both branches return Int
if condition then
1
else
0
Try itInvalid example (branches have different types):
if False then 1 else "zero" -- Error: type mismatch
Try itMulti-line If
let condition = True
if condition then
"yes"
else
"no"
Try itMulti-way Conditionals
Chain conditions with else if:
let score = 85
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"
grade -- "B"
Try itCase Expressions
Pattern match on values with case:
type Color = Red | Green | Blue
let color = Color::Green
let description =
case color of
Color::Red -> "The color of fire"
Color::Green -> "The color of nature"
Color::Blue -> "The color of sky"
description
Try itQualified Enum Patterns
Patterns support the same qualified :: syntax used in expressions:
type Color = Red | Green | Blue
let color = Color::Green
case color of
Color::Red -> "fire"
Color::Green -> "nature"
Color::Blue -> "sky"
Try itMatching Multiple Patterns
Use | to match multiple patterns (or-patterns):
type Day
= Monday
| Tuesday
| Wednesday
| Thursday
| Friday
| Saturday
| Sunday
let day = Day::Saturday
let isWeekend =
case day of
Day::Saturday | Day::Sunday -> True
_ -> False
isWeekend -- True
Try itMatching with Guards
Add conditions to patterns using if:
let number = -5
case number of
n if n < 0 -> "negative"
n if n == 0 -> "zero"
n if n > 0 -> "positive"
_ -> "unknown"
Try itMatching Nested Structures
Pattern match deeply nested data:
let data = (3, 4)
case data of
(a, b) -> a + b
Try itMaybe Handling
Handle optional values with case:
let maybeUser = Just "Alice"
let displayName =
case maybeUser of
Just name -> name
Nothing -> "Anonymous"
displayName
Try itThe Maybe type is defined as:
-- norun
type Maybe a = Just(a) | Nothing
Result Handling
Handle success/failure with Result:
import String
let parseResult = Ok 42
case parseResult of
Ok n -> "Parsed: " ++ String.fromInt n
Err msg -> "Error: " ++ msg
Try itThe Result type is defined as:
-- norun
type Result a e = Ok(a) | Err(e)
List Pattern Matching
Destructure lists in case expressions:
let numbers =
[1, 2, 3]
case numbers of
[] -> "empty list"
[x] -> "single element"
[x, y] -> "two elements"
x :: xs -> "multiple elements"
Try itProcessing Lists Recursively
fn sum: [Int] -> Int
fn sum list =
case list of
[] -> 0
x :: xs -> x + sum xs
sum [1, 2, 3, 4, 5] -- 15
Try itMultiple Head Elements
let list = [1, 2, 3, 4]
case list of
a :: b :: rest -> a + b
x :: xs -> x
[] -> 0
Try itExhaustiveness Checking
The compiler ensures all cases are handled:
type Color = Red | Green | Blue
let color = Color::Blue
case color of
Color::Red -> "red"
Color::Green -> "green"
Color::Blue -> "blue"
Try itUse _ for catch-all patterns:
type Color = Red | Green | Blue
let color = Color::Green
case color of
Color::Red -> "primary"
_ -> "other"
Try itGuard Exhaustiveness
Add a catch-all pattern to ensure completeness:
let x = 0
case x of
n if n > 0 -> "positive"
n if n < 0 -> "negative"
_ -> "zero"
Try itBest Practices
- Prefer case over if for matching on types
- Handle all cases explicitly when possible
- Use guards for complex conditions
- Keep pattern matches shallow — deep nesting is hard to read
- Put specific patterns first, catch-all patterns last
Next Steps
Learn about collections to work with lists, tuples, and records.