Esc
Start typing to search...

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 it

Both branches must have the same type:

let condition = True

-- Valid: both branches return Int
if condition then 1 else 0
Try it

Invalid example (branches have different types):

if condition then 1 else "zero"  -- Error: type mismatch

Multi-line If

let condition = True

if condition then
    "yes"
else
    "no"
Try it

Multi-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 it

Case Expressions

Pattern match on values with case:

type Color = Red | Green | Blue

let color = Green

let description = case color of
    Red   -> "The color of fire"
    Green -> "The color of nature"
    Blue  -> "The color of sky"

description

Qualified 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"

Matching Multiple Patterns

Use | to match multiple patterns (or-patterns):

type Day = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday

let day = Saturday

let isWeekend = case day of
    Saturday | Sunday -> True
    _ -> False

isWeekend  -- True

Matching 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 it

Matching Nested Structures

Pattern match deeply nested data:

let data = (3, 4)

case data of
    (a, b) -> a + b
Try it

Maybe Handling

Handle optional values with case:

let maybeUser = Just "Alice"

let displayName = case maybeUser of
    Just name -> name
    Nothing   -> "Anonymous"

displayName
Try it

The Maybe type is defined as:

type Maybe a = Just a | Nothing

Result Handling

Handle success/failure with Result:

let parseResult = Ok 42

case parseResult of
    Ok n    -> "Parsed: " ++ show n
    Err msg -> "Error: " ++ msg

The Result type is defined as:

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 it

Processing Lists Recursively

fn sum : List Int -> Int
fn sum list = case list of
    []      -> 0
    x :: xs -> x + sum xs

sum [1, 2, 3, 4, 5]  -- 15
Try it

Multiple Head Elements

let list = [1, 2, 3, 4]

case list of
    a :: b :: rest -> a + b
    x :: xs -> x
    [] -> 0
Try it

Exhaustiveness Checking

The compiler ensures all cases are handled:

type Color = Red | Green | Blue

let color = Blue

case color of
    Red   -> "red"
    Green -> "green"
    Blue  -> "blue"

Use _ for catch-all patterns:

type Color = Red | Green | Blue

let color = Green

case color of
    Red -> "primary"
    _   -> "other"

Guard 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 it

Best Practices

  1. Prefer case over if for matching on types
  2. Handle all cases explicitly when possible
  3. Use guards for complex conditions
  4. Keep pattern matches shallow — deep nesting is hard to read
  5. Put specific patterns first, catch-all patterns last

Next Steps

Learn about collections to work with lists, tuples, and records.