Pre-release Harn is pre-1.0 — the language, standard library, and CLI may change between releases. See the release notes

Control flow

if/else

if condition {
  // then
} else if other {
  // else-if
} else {
  // else
}

else if chains are parsed as a nested ifElse node in the else branch.

for/in

for item in iterable {
  // body
}

If iterable is a list, iterates over elements. If iterable is a dict, iterates over entries sorted by key, where each entry is {key: "...", value: ...}. The loop variable is mutable within the loop body.

while

while condition {
  // body
}

Maximum 10,000 iterations (safety limit). Condition is re-evaluated each iteration.

A while true { ... } whose body contains no break binding to that loop types as never (see The never type): control can only leave it through return/throw, so a function whose tail is such a loop does not need a trailing return, and statements after the loop are flagged unreachable.

match

match is an expression. It can be used as a standalone statement or in expression position; the selected arm evaluates to the value of its block's last expression.

match value {
  pattern1 -> { body1 }
  pattern2 if condition -> { body2 }
}

Patterns are expressions. Each pattern is evaluated and compared to the match value using valuesEqual. An arm may include an if guard after the pattern; when present, the arm only matches if the pattern matches and the guard expression evaluates to a truthy value. The first matching arm executes.

If no arm matches, a runtime error is thrown (No match arm matched the value). This makes non-exhaustive matches a hard failure rather than a silent nil.

const x = 5
const label = match x {
  1 -> { "one" }
  n if n > 3 -> { "big: ${n}" }
  _ -> { "other" }
}
// label == "big: 5"

List patterns

A list pattern [p0, p1, …] matches a list of exactly that length and binds each identifier element to the value at its position (literals are compared, _ discards). A trailing ...rest element makes the pattern match a list of at least the leading arity and binds the remainder as a new list (..._ matches the tail without binding it):

match coords {
  [x, y] -> { "2d: ${x},${y}" }          // matches ONLY length 2
  [x, y, z] -> { "3d" }                   // matches ONLY length 3
  [first, ...rest] -> { "${first}+${rest}" } // length >= 1; rest is a list
  [] -> { "empty" }
  _ -> { "other" }
}

When the matched value is a list<T>, the leading bindings have type T and a ...rest binding has type list<T>. Only one ...rest is allowed and it must be last.

retry

retry 3 {
  // body that may throw
}

Executes the body up to N times. If the body succeeds (no error), returns immediately. If the body throws, catches the error and retries. return statements inside retry propagate out (are not retried). After all attempts are exhausted, returns nil (does not re-throw the last error).