Skip to content

Prefix Notation

Prefix notation is ilo’s core token-saving device. Operators come before their operands.

Infix (traditional)Prefix (ilo)Chars saved
(a * b) + c+*a b c4
((a + b) * c) >= 100>=*+a b c 1007
a > 0>a 00

Across 25 expression patterns: 22% fewer tokens, 42% fewer characters vs infix.

Prefix eliminates:

  • Parentheses for grouping
  • Operator precedence ambiguity
  • Nesting depth
ilo
(a + b) * c -- Infix: 5 tokens, 13 chars
*+a b c -- Prefix: 4 tokens, 7 chars

Read inside-out, left-to-right:

ExpressionMeaning
+a badd a and b
* _ cmultiply result and c

ilo supports infix too:

ilo
a + b -- works
x * y + 1 -- works
PrefixInfixMeaningTypes
+a ba + badd / concat / list concatn, t, L
+=a va += vappend to listL
-a ba - bsubtractn
*a ba * bmultiplyn
/a ba / bdividen
=a ba = bequalany
!=a ba != bnot equalany
>a ba > bgreater thann, t
<a ba < bless thann, t
>=a ba >= bgreater or equaln, t
<=a ba <= bless or equaln, t
&a ba & blogical AND (short-circuit)any (truthy)
|a ba | blogical OR (short-circuit)any (truthy)
OpMeaningTypes
-xnegaten
!xlogical NOTany (truthy)
OpMeaning
a??bnil-coalesce (if a is nil, return b)
a>>fpipe (desugars to f(a))

When using infix, standard mathematical precedence applies (higher binds tighter):

LevelOperators
6* /
5+ -
4> < >= <=
3= !=
2&
1|

Function application binds tighter than all infix operators:

ilo
f a + b -- means (f a) + b, NOT f(a + b)

Measured across 25 expression patterns using the cl100k_base tokenizer (used by Claude):

PatternInfixPrefixChar savings
Binary opa + b+a b1 char
Nested (2 deep)(a * b) + c+*a b c4 chars
Nested (3 deep)((a + b) * c) >= 100>=*+a b c 1006 chars
Chained comparisonx >= 0 and x <= 100&>=x 0 <=x 1004 chars
Negationnot (a == b)!=a b8 chars

The key insight: each nested prefix operator saves 2 characters (no parentheses needed). Flat binary expressions save 1 character. The deeper the nesting, the bigger the savings.

Overall: 22% fewer tokens, 42% fewer characters vs infix.

Prefix operators nest by each operator greedily consuming the next two operands. Those operands can themselves be operator expressions:

ilo
-- Two levels: (a * b) + c
+*a b c
-- Two levels, right-heavy: a * (b + c)
*a +b c
-- Three levels: ((a + b) * c) >= 100
>=*+a b c 100
-- Symmetric: (a * b) - (c * d)
-*a b *c d
-- Four levels: (((a + b) * c) - d) / e
/-*+a b c d e
-- Mixed comparisons: (x + y) >= (a * b)
>=+x y *a b

Read left-to-right. Each operator grabs the next two values (which may themselves be operator expressions):

/-*+a b c d e

StepExpressionMeaning
1+a badd a and b
2* _ cmultiply result and c
3- _ dsubtract d from result
4/ _ edivide result by e

This is equivalent to (((a + b) * c) - d) / e in infix - note how 4 pairs of parentheses disappear entirely.

Operator operands must be atoms (literals, variable references, field access) or nested prefix operators. Function calls are not valid operands - bind their results first:

ilo
-- WRONG: *n fac -n 1 (fac is treated as an atom, not a call)
-- RIGHT: r=fac -n 1;*n r (bind the call result, then use it)