Skip to content

Collections

Use this when working with lists, maps, or other collection operations.

Lists use L followed by the element type: L n (list of numbers), L t (list of text). Elements are separated by spaces or commas. Variables and expressions work as elements.

ilo
nums=[1 2 3 4 5] -- L n (list of numbers)
words=["hi" "bye"] -- L t (list of text)
w="world"
greet=["hi" w] -- L t (variables work too)
args=["search" 10 true] -- L _ (mixed types)

Built-in functions for working with lists:

ilo
len nums -- → 5
hd nums -- → 1 (first element)
tl nums -- → [2 3 4 5] (rest)
nums.2 -- → 3 (dot-notation, zero-indexed)
+=nums 6 -- → [1 2 3 4 5 6] (append)
rev nums -- → [5 4 3 2 1] (reverse)
srt nums -- → [1 2 3 4 5] (sort ascending)
rev(srt nums) -- → [5 4 3 2 1] (sort descending)

srt also accepts a key function for sort-by (srt fn list):

ilo
neg x:n>n;-x -- negate a number
srt neg nums -- sort by negative → [5 4 3 1 1]
slen s:t>n;len s -- string length as key
srt slen ["banana" "fig" "apple" "kiwi"] -- → [fig kiwi apple banana]

Number functions that pair well with lists:

ilo
flr 3.7 -- → 3 (floor)
cel 3.2 -- → 4 (ceiling)
rou 3.5 -- → 4 (round)

Pass functions to map, flt, and fld to transform, filter, and reduce lists. Most builtins have a short and long form, both work: flt or filter, fld or fold, srt or sort, etc. See the full alias table. The examples below use short forms inline and long forms in multiline.

map fn list applies fn to every element, returning a new list:

ilo
dbl x:n>n;*x 2 main xs:L n>L n;map dbl xs -- → [2 4 6 8 10]

Or as a file:

ilo
double x:n > n -- double a number
*x 2 -- multiply x by 2
main nums:L n > L n -- map double over a list
map double nums -- → [2 4 6 8 10]

flt fn list keeps only elements where fn returns true:

ilo
pos x:n>b;>x 0 main xs:L n>L n;flt pos xs -- → [1 2 3 4 5]

Or as a file:

ilo
positive x:n > b -- is positive?
> x 0 -- x greater than 0
main nums:L n > L n -- keep only positives
filter positive nums -- → [1 2 3 4 5]

fld fn init list reduces a list to a single value. Applies fn to an accumulator and each element left-to-right. Like reduce in JavaScript or foldl in Haskell:

ilo
add a:n b:n>n;+a b main xs:L n>n;fld add 0 xs -- → 15

Or as a file:

ilo
add a:n b:n > n -- add two numbers
+a b -- return a + b
main nums:L n > n -- sum via fold
fold add 0 nums -- → 15

Shorthand for folding with addition:

ilo
sum nums -- → 15

Instead of declaring a one-off helper, pass a function literal directly to any HOF:

ilo
by-dist xs:L n>L n;srt (x:n>n;abs x) xs -- sort by distance from zero
nonempty ws:L t>L t;flt (s:t>b;>(len s) 0) ws -- keep non-empty strings
sumsq xs:L n>n;fld (a:n x:n>n;+a *x x) xs 0 -- sum of squares

Syntax is the same as a top-level function declaration, wrapped in parens, no name: (<param>:<type> ...><return-type>;<body>). The body can capture variables from the enclosing scope:

ilo
above xs:L n thr:n>L n;flt (x:n>b;>x thr) xs -- captures `thr`

Access elements by index (zero-based) using .. Works on lists and maps:

ilo
xs.0 -- first element
xs.2 -- third element
data.users.0 -- chained access

Safe navigation with .? returns nil instead of erroring:

ilo
user.?email -- nil if "email" doesn't exist

@ is ilo’s loop construct. It iterates over a list or range, and returns the last iteration’s value:

ilo
sq-last xs:L n>n;@x xs{*x x}
Shell
ilo 'sq-last xs:L n>n;@x xs{*x x}' 3,4,5
# → 25 (last element 5, squared)

Use @ with a range to loop over numbers:

Shell
ilo 'f>n;s=0;@i 0..5{s=+s i};s' f
# → 10 (sum of 0+1+2+3+4)

Use braces when the body has multiple statements:

Shell
ilo 'f>L n;xs=[];@i 0..3{xs=+=xs i};xs' f
# → [0, 1, 2]

Common operations for working with lists and text:

slc xs a b returns elements from index a up to (but not including) b:

Shell
ilo 'f xs:L n>L n;slc xs 1 3' 10,20,30,40
# → [20, 30]

Negative indices count from the end (Python-style); bounds clamp instead of wrapping:

Shell
ilo 'f xs:L n>L n;slc xs 0 -1' 10,20,30,40 # drop the last
# → [10, 20, 30]
ilo 'f xs:L n>L n;slc xs -2 (len xs)' 10,20,30,40 # keep last two
# → [30, 40]

at xs i, take n xs, and drop n xs follow the same rule. take -1 xs keeps all but the last element; drop -1 xs keeps only the last.

has xs v tests membership. Works on lists (element check) and text (substring check):

Shell
ilo 'f xs:L n>b;has xs 3' 1,2,3,4
# → true
ilo 'f s:t>b;has s "llo"' "hello"
# → true

+=xs v appends an element to a list:

Shell
ilo 'f xs:L n>L n;+=xs 99' 1,2,3
# → [1, 2, 3, 99]

Maps are key-value collections, like dictionaries in Python or objects in JavaScript. Keys are typed: text (t) or integer (n). Int(1) and Text("1") are distinct, so a numeric index map and a string-keyed map can’t accidentally collide. Maps are immutable: mset and mdel return new maps rather than modifying in place.

ilo
idx=mmap
idx=mset idx 7 "seven" -- integer key, no str conversion
mget idx 7 -- → "seven"
mhas idx "7" -- → false (Int and Text are distinct)
ilo
scores>n
m=mmap
m=mset m "alice" 99
m=mset m "bob" 87
mget m "alice"
Shell
ilo 'scores>n;m=mmap;m=mset m "alice" 99;m=mset m "bob" 87;mget m "alice"' scores
# → 99
CallReturnsMeaning
mmapM t _create empty map
mset m k vM k vnew map with key set
mget m kvalue or nilvalue at key
mhas m kbkey exists?
mkeys mL tsorted list of keys
mvals mL vvalues sorted by key
mdel m kM k vnew map without key
len mnnumber of entries
Shell
ilo 'check>b;m=mset mmap "x" "1";mhas m "x"' check
# → true

Functions that reduce a collection to a single value:

Shell
ilo 'f xs:L n>n;sum xs' 1,2,3,4,5
# → 15
ilo 'f xs:L n>n;avg xs' 2,4,6
# → 4

grp fn xs groups a list by a key function, returning M t (L a):

ilo
cl x:n>t;>x 5{"big"}{"small"}
classify xs:L n>M t L n;grp cl xs
Shell
ilo 'cl x:n>t;>x 5{"big"}{"small"} classify xs:L n>M t L n;grp cl xs' classify 1,3,7,10,2
# → {big: [7, 10], small: [1, 3, 2]}

Flattens one level of nesting:

Shell
ilo 'f>L n;flat [[1, 2], [3], [4, 5]]' f
# → [1, 2, 3, 4, 5]

Removes duplicates while preserving order:

Shell
ilo 'f xs:L t>L t;unq xs' a,b,a,c,b
# → [a, b, c]
FunctionAliasSignatureDescription
lenlengthL _ > nList length
hdheadL _ > _First element
tltailL _ > L _All elements except first
atL _ n > _i-th element (0-indexed; negative counts from end; float i auto-floors)
revreverseL _ > L _Reverse a list
srtsortL _ > L _Sort a list
srtsortfn L _ > L _Sort by key function
slcsliceL _ n n > L _Slice (start, end)
flatflattenL L _ > L _Flatten one level of nesting
unquniqueL _ > L _Remove duplicates
hascontainsL _ _ > bMembership
mapfn L _ > L _Apply function to each element
maprfn L _ > R (L _) _Map with short-circuit Result propagation: collects Ok values, returns the first Err
fltfilterfn L _ > L _Keep elements where function returns true
ctcountfn L _ > nCount elements where predicate returns true. Allocation-free vs len (flt fn xs).
fldfoldfn _ L _ > _Reduce list to single value
grpgroupfn L _ > M t L _Group elements by function result
mmap> MCreate empty map
mgetM t > _Get value by key
msetM t _ > MSet key-value pair
mhasM t > bCheck if key exists
mkeysM > L tGet all keys
mvalsM > L _Get all values
mdelM t > MRemove key, return new map

The variable-index form xs.i is sugar for at xs i. The parser builds a field-access node and a post-parse desugar pass rewrites it whenever the field identifier resolves to a binding in the current scope (parameter, let, foreach, range, match-arm). If the identifier is also a declared field on a record type, the rewrite is skipped and the strict .field record-access semantics apply.

  • Text for string operations (trm, spl, fmt, cat, rgx)
  • Numbers for numeric aggregation (min, max, median, stdev)