Velo

Control flow

Conditional Statements

If (Statement)

The everyday form: a condition and a block body, run for its side effects. There is no then, and the else branch is optional. Parentheses around the condition are optional:

if score >= 60 {
    term.println("pass")
} else {
    term.println("fail")
}

if (ready) {
    start()          # no else needed
}

If-then-else (Expression)

if is also an expression that yields a value. In this form the keyword then introduces the value, and else is required (there must be a value on every path):

int a = 5
str result = if a == 2 then "two" else "not two"

If-then-else (Block)

int a = 5
str result = if a == 2 then {
    "two"
} else {
    "not two"
}

Nested Conditionals

int score = 85
str grade = if score >= 90 then {
    "A"
} else if score >= 80 then {
    "B"
} else if score >= 70 then {
    "C"
} else {
    "F"
}

Pattern matching with when

when is the multi-way branch — a switch and a pattern match in one form. Like if, it is an expression: each arm's body yields a value (a block yields its last expression, no return). Arms are pattern -> body, one per line (a comma between arms is also allowed), and else -> is the catch-all.

Over a primitive (int, byte, long, float, str, bool) the patterns are literals compared with == — the same comparison if uses (matching a float is exact equality, with the usual precision caveat). An else is required, except a bool that covers both true and false (a closed domain):

str name = when day {
    0 -> "Sunday"
    6 -> "Saturday"
    else -> "weekday"
}

str answer = when ok {
    true  -> "yes"
    false -> "no"
}

Sum types — enum

An enum is a closed set of variants, each a value-type record (immutable, compared by value, transferable across actors — like a data class). A variant may carry fields, and may reference the enum recursively:

enum Expr {
    Lit(int n)
    Add(Expr l, Expr r)
    Neg(Expr e)
}

Expr program = new Add(new Lit(3), new Neg(new Lit(1)))

A when over an enum matches variants and binds their fields positionally. The compiler checks the arms are exhaustive — every variant must be covered, or an else provided — so a forgotten case is a compile error, not a runtime bug:

func eval(Expr e) int {
    return when e {
        Lit(n)    -> n
        Add(l, r) -> eval(l) + eval(r)
        Neg(x)    -> 0 - eval(x)
    }
}

A variant with no fields is written bare in a pattern and constructed with new:

enum Color { Red, Green, Blue }

str hex = when c {
    Red   -> "#f00"
    Green -> "#0f0"
    Blue  -> "#00f"
}
# ... constructed as: Color c = new Green()

What when matches — and what it doesn't

A pattern goes one level deep: a variant name and its immediate field bindings. There is deliberately no nested-pattern matching and no arm guard — both are expressible with what the language already has, at no extra cost:

# Nested match: bind one level, then match the field with another `when`.
when e {
    Add(l, r) -> when l {
        Lit(a) -> a
        else   -> 0
    }
    Lit(n) -> n
    Neg(x) -> 0 - eval(x)
}

# A condition on a variant: use `if` inside the arm body.
when e {
    Lit(n) -> if n > 0 then "positive" else "non-positive"
    Add(l, r) -> "sum"
    Neg(x) -> "negated"
}

Enums are not generic in this version (enum Option[T] { ... } is not supported) — a variant's fields take concrete types.

Arms are separated by a newline, or a comma. Prefer commas when a pattern begins with an operator (a negative literal), so it is not read as a continuation of the previous arm:

str s = when n { 1 -> "one", -1 -> "neg", else -> "other" }