Guards & Control Flow
ilo uses guards instead of if/else. Guards are flat statements that return early - no nesting, no closing braces.
Basic guards
Section titled “Basic guards”Inline (semicolons replace newlines):
grd s:n>t;>=s 90 "A";>=s 80 "B";>=s 70 "C";"F"Or the same thing as a file (newlines and indentation):
grade score:n > t -- number in, text out >= score 90 "A" -- if score >= 90, return "A" >= score 80 "B" -- if score >= 80, return "B" >= score 70 "C" -- if score >= 70, return "C" "F" -- fallbackEach guard checks a condition. If true, the function returns that value. Otherwise, execution continues.
Why guards?
Section titled “Why guards?”Compare the Python equivalent:
if score >= 90: return "A"elif score >= 80: return "B"elif score >= 70: return "C"else: return "F"Guards eliminate if/elif/else keywords, braces, and deep nesting - fewer tokens to generate, fewer places for AI agents to make mistakes. Every guard chain must end with a fallback value, so you can’t have an open-ended switch that silently returns nothing.
Boolean guards
Section titled “Boolean guards”Inline:
chk x:n>t;==x 0 "zero";>x 0 "positive";"negative"Or as a file:
check x:n > t -- number in, text out == x 0 "zero" -- if x equals 0, return "zero" > x 0 "positive" -- if x > 0, return "positive" "negative" -- fallbackNegated guards
Section titled “Negated guards”Prefix a guard with ! to negate the condition:
Inline:
f x:n>n;!>x 0 0;xOr as a file:
f x:n > n -- number in, number out !> x 0 0 -- if x not greater than 0, return 0 x -- otherwise return xIf x is NOT greater than 0, return 0. Otherwise return x.
ilo 'f x:n>n;!>x 0 0;x' 5# → 5
ilo 'f x:n>n;!>x 0 0;x' -3# → 0Negated braceless guards work with any comparison. Use ^ (throw) to return an error instead of a value: !<=n 0 ^"must be positive".
Braced guards
Section titled “Braced guards”The return value can be enclosed in braces. Both forms produce identical results:
>=sp 1000 "gold" -- braceless>=sp 1000{"gold"} -- braced (identical result)Use braces when the body has multiple statements:
>=sp 1000{a=classify sp;a}Ternary
Section titled “Ternary”Like x == 0 ? 10 : 20 in JS/C/Go, a ternary produces a value. Unlike guards, it does not return early - code after it keeps running, unless it’s the last expression in the function.
Prefix ternary
Section titled “Prefix ternary”? followed by a comparison operator, then the true and false values:
f x:n>n;?=x 0 10 20ilo 'f x:n>n;?=x 0 10 20' f 0# → 10
ilo 'f x:n>n;?=x 0 10 20' f 5# → 20The condition must start with a comparison operator (=, >, <, >=, <=, !=). You can assign the result:
f x:n>n;v=?=x 0 10 20;+v 1 -- v is 10 or 20, then add 1Braced ternary
Section titled “Braced ternary”A guard with two brace blocks - {then}{else}:
f x:n>t;=x 1{"yes"}{"no"}ilo 'f x:n>t;=x 1{"yes"}{"no"}' 1# → yes
ilo 'f x:n>t;=x 1{"yes"}{"no"}' 2# → noSupports negation: !=x 1{"not one"}{"one"}.
Match expressions
Section titled “Match expressions”For value matching:
Inline:
describe x:t>t;?x{"dog":"woof";"cat":"meow";_:"unknown"}Or as a file:
describe x:t > t -- text in, text out ? x { -- match on x "dog": "woof" -- if x is "dog", return "woof" "cat": "meow" -- if x is "cat", return "meow" _: "unknown" -- wildcard: catches everything else } -- end match_ is the wildcard arm. No fall-through; each arm is independent.
Matching on Result types
Section titled “Matching on Result types”Functions that can fail return R ok_type err_type (a Result). A Result isn’t a parameter type — it comes from calling a fallible function and capturing the return value. Use match to handle both cases:
~v— matches the Ok variant, binds the inner value tov^e— matches the Err variant, binds the error toe
Inline:
div a:n b:n>R n t;=b 0 ^"divide by zero";~/a bshow a:n b:n>t;r=div a b;?r{~v:str v;^e:e}Or as a file:
div a:n b:n > R n t -- two numbers in, Result out = b 0 ^"divide by zero" -- if b is 0, return Err ~/a b -- otherwise return Ok(a / b)
show a:n b:n > t -- two numbers in, text out r = div a b -- call div, capture Result in r ? r { -- match on the Result ~v: str v -- Ok: convert number to text ^e: e -- Err: return error message } -- end matchdiv returns R n t — either an Ok number or an Err string. show captures the Result in r (without auto-unwrapping) and matches on it.
ilo 'div a:n b:n>R n t;=b 0 ^"divide by zero";~/a bshow a:n b:n>t;r=div a b;?r{~v:str v;^e:e}' show 10 2# → 5
ilo 'div a:n b:n>R n t;=b 0 ^"divide by zero";~/a bshow a:n b:n>t;r=div a b;?r{~v:str v;^e:e}' show 10 0# → divide by zeroSee Error Handling for more on ~ (Ok), ^ (Err), and the R type.
Matching on type
Section titled “Matching on type”When the input type is unknown (_), match on the runtime type:
Inline:
f x:_>t;?x{n v:"number";t v:"text";_:"other"}Or as a file:
f x:_ > t -- any type in, text out ? x { -- match on runtime type n v: "number" -- if x is a number t v: "text" -- if x is text _: "other" -- anything else } -- end matchEach arm specifies a type tag (n, t, b, l) followed by a binding variable.
Early return with ret
Section titled “Early return with ret”The last expression in a function is its return value, no return keyword needed. Guards also return early when a condition matches. Use ret when you need an explicit early return from inside a loop or braced block:
Inline:
f x:n>n;>x 0{ret x};0Or as a file:
f x:n > n -- number in, number out > x 0 { ret x } -- if x > 0, return x early 0 -- fallbackGuards already provide early return for simple cases. Use ret when you need early return inside a loop or deeply nested block:
Inline:
f xs:L n>n;@x xs{>=x 10{ret x}};0 -- return first element >= 10Or as a file:
f xs:L n > n -- list of numbers in, number out @x xs { -- loop over list >= x 10 { ret x } -- if x >= 10, return it early } -- end loop 0 -- fallback if none found