Tzopilotl
Docs
GitHub

Built-in Functions Reference

This document lists all built-in functions available in the language. Functions are overloaded on their argument types — many function names accept multiple type signatures. Built-in functions are real-time safe unless marked NRT (currently the File & OS and FFT sections); calling an NRT function in code compiled for a real-time VM is a compile-time error.

Mutating-function convention. Function names ending in ! mutate their first argument in place; names without ! return new values and leave their inputs untouched. ! is part of the identifier — push and push! are different functions. The convention is not enforced; users may name a mutating function with or without ! as they prefer.

Table of Contents

General Numeric Functions

These functions are overloaded across multiple numeric types.

abs(x)

Returns the absolute value of x.

SignatureDescription
abs(Int) → IntAbsolute value of an integer.
abs(Float) → FloatAbsolute value of a float.
abs(Fraction) → FractionAbsolute value of a fraction.
abs(Complex) → FloatMagnitude (modulus) of a complex number.
-5 abs           -- 5
-3.14 abs        -- 3.14
(3+4i) abs       -- 5.0

sign(x)

Returns -1, 0, or 1 indicating the sign of x.

SignatureDescription
sign(Int) → IntSign of an integer.
sign(Float) → IntSign of a float.
sign(Fraction) → IntSign of a fraction.

min(a, b)

Returns the smaller of two values.

SignatureDescription
min(Int, Int) → IntMinimum of two integers.
min(Float, Float) → FloatMinimum of two floats.
min(Fraction, Fraction) → FractionMinimum of two fractions.
min(String, String) → StringLexicographically smaller of two strings.

max(a, b)

Returns the larger of two values.

SignatureDescription
max(Int, Int) → IntMaximum of two integers.
max(Float, Float) → FloatMaximum of two floats.
max(Fraction, Fraction) → FractionMaximum of two fractions.
max(String, String) → StringLexicographically larger of two strings.

clamp(x, lo, hi)

Clamps x to the range [lo, hi].

SignatureDescription
clamp(Int, Int, Int) → IntClamp an integer to a range.
clamp(Float, Float, Float) → FloatClamp a float to a range.
clamp(Fraction, Fraction, Fraction) → FractionClamp a fraction to a range.
15 clamp(0, 10)      -- 10
-5 clamp(0, 10)      -- 0
5 clamp(0, 10)       -- 5

cmp(a, b)

Three-way comparison. Returns -1 if a < b, 0 if a == b, 1 if a > b.

SignatureDescription
cmp(Int, Int) → IntCompare two integers.
cmp(Float, Float) → IntCompare two floats.
cmp(Fraction, Fraction) → IntCompare two fractions.
cmp(String, String) → IntLexicographic comparison of two strings.

gcd(a, b)

Greatest common divisor of two integers. Always returns a non-negative result.

SignatureDescription
gcd(Int, Int) → IntGreatest common divisor.
gcd(12, 8)     -- 4
gcd(7, 13)     -- 1
gcd(0, 5)      -- 5
gcd(-12, 8)    -- 4

lcm(a, b)

Least common multiple of two integers. Returns 0 if either argument is 0. Always returns a non-negative result.

SignatureDescription
lcm(Int, Int) → IntLeast common multiple.
lcm(4, 6)      -- 12
lcm(7, 13)     -- 91
lcm(0, 5)      -- 0
lcm(-4, 6)     -- 12
↑ Back to top

Random Number Generation

Random number functions use a per-VM xoshiro256** generator, seeded from the system entropy source at VM creation. Each VM instance has its own independent RNG state. These functions are never constant-folded.

urand()

Returns a uniform random Float in the range [0.0, 1.0).

SignatureDescription
urand() → FloatUniform random float in [0.0, 1.0).
urand()    -- e.g. 0.7320508075688772

urands() / urands(n)

Multiple uniform random floats in [0.0, 1.0). With no arguments, returns an infinite lazy list. With one argument, returns an array of n values.

SignatureDescription
urands() → List<Float>Infinite lazy list of uniform random floats.
urands(Int) → [Float]Array of n uniform random floats.
urands() take(3)   -- e.g. List(0.312, 0.449, 0.641)
urands(4)          -- e.g. [0.041, 0.718, 0.013, 0.554]

brand()

Returns a bipolar random Float in the range [-1.0, 1.0).

SignatureDescription
brand() → FloatBipolar random float in [-1.0, 1.0).
brand()    -- e.g. -0.4142135623730951

brands() / brands(n)

Multiple bipolar random floats in [-1.0, 1.0). With no arguments, returns an infinite lazy list. With one argument, returns an array of n values.

SignatureDescription
brands() → List<Float>Infinite lazy list of bipolar random floats.
brands(Int) → [Float]Array of n bipolar random floats.
brands() take(3)   -- e.g. List(-0.945, 0.620, -0.570)
brands(4)          -- e.g. [0.232, -0.846, -0.553, 0.714]

irand(lo, hi)

Returns a uniform random Int in the inclusive range [lo, hi]. Uses unbiased rejection sampling. If lo > hi, the arguments are swapped.

SignatureDescription
irand(Int, Int) → IntUniform random integer in [lo, hi].
irand(1, 6)      -- e.g. 4 (simulates a die roll)
irand(0, 99)     -- e.g. 42

irands(lo, hi) / irands(n, lo, hi)

Multiple uniform random integers in [lo, hi]. With two arguments, returns an infinite lazy list. With three arguments, the first is the count n and returns an array.

SignatureDescription
irands(Int, Int) → List<Int>Infinite lazy list of uniform random integers in [lo, hi].
irands(Int, Int, Int) → [Int]Array of n uniform random integers in [lo, hi].
irands(1, 6) take(5)   -- e.g. List(3, 6, 1, 4, 2)
irands(4, 1, 6)        -- e.g. [5, 2, 6, 1]

rand(lo, hi)

Returns a uniform random Float in the range [lo, hi).

SignatureDescription
rand(Float, Float) → FloatUniform random float in [lo, hi).
rand(5.0, 10.0)    -- e.g. 7.764822845349203
rand(-1.0, 1.0)    -- e.g. -0.271028337412

rands(lo, hi) / rands(n, lo, hi)

Multiple uniform random floats in [lo, hi). With two arguments, returns an infinite lazy list. With three arguments, the first is the count n and returns an array.

SignatureDescription
rands(Float, Float) → List<Float>Infinite lazy list of uniform random floats in [lo, hi).
rands(Int, Float, Float) → [Float]Array of n uniform random floats in [lo, hi).
rands(5.0, 10.0) take(3)   -- e.g. List(8.41, 7.51, 7.24)
rands(4, 5.0, 10.0)        -- e.g. [6.07, 5.49, 6.27, 6.92]

xrand(lo, hi)

Returns an exponentially distributed random Float in [lo, hi). Computed as lo * pow(hi/lo, urand()). Useful for generating values that span several orders of magnitude with equal probability per octave (e.g. frequencies, durations).

SignatureDescription
xrand(Float, Float) → FloatExponentially distributed float in [lo, hi).
xrand(20.0, 20000.0)   -- e.g. 632.4555320336759 (random frequency)
xrand(0.01, 10.0)      -- e.g. 0.2154434690031882 (random duration)

xrands(lo, hi) / xrands(n, lo, hi)

Multiple exponentially distributed random floats in [lo, hi). With two arguments, returns an infinite lazy list. With three arguments, the first is the count n and returns an array.

SignatureDescription
xrands(Float, Float) → List<Float>Infinite lazy list of exponentially distributed floats in [lo, hi).
xrands(Int, Float, Float) → [Float]Array of n exponentially distributed floats in [lo, hi).
xrands(20.0, 20000.0) take(3)   -- e.g. List(632.5, 47.8, 8841.2)
xrands(4, 0.01, 10.0)           -- e.g. [0.215, 3.42, 0.078, 1.56]

pick(a)

Returns a randomly chosen element from the array a.

SignatureDescription
pick([T]) → TChoose a random element from the array.
pick([1, 2, 3, 4, 5])        -- e.g. 3
pick(["red", "green", "blue"])  -- e.g. "green"

picks(a) / picks(a, n)

Returns random elements chosen from the array a. With one argument, returns an infinite lazy list of random picks. With two arguments, returns an array of n random picks.

SignatureDescription
picks([T]) → List<T>Infinite lazy list of random picks from the array.
picks([T], Int) → [T]Array of n random picks from the array.
picks([1, 2, 3]) take(5)     -- e.g. List(2, 1, 3, 3, 1)
picks(["a", "b", "c"], 4)   -- e.g. ["b", "a", "c", "a"]
↑ Back to top

Rounding Functions

All rounding functions take a Float and return a Float.

FunctionSignatureDescription
floor(x)Float → FloatLargest integer value ≤ x.
ceil(x)Float → FloatSmallest integer value ≥ x.
round(x)Float → FloatRound to nearest integer (halfway cases round away from zero).
trunc(x)Float → FloatRound toward zero (drop the fractional part).
frac(x)Float → FloatFractional part: x - floor(x).
3.7 floor     -- 3.0
3.2 ceil      -- 4.0
2.5 round     -- 3.0
-3.7 trunc    -- -3.0
3.75 frac     -- 0.75
↑ Back to top

Powers & Roots

FunctionSignatureDescription
sqrt(x)Float → FloatSquare root.
sqrt(z)Complex → ComplexComplex square root.
cbrt(x)Float → FloatCube root.
pow(x, y)(Float, Float) → FloatRaise x to the power y.
pow(n, e)(Int, Int) → IntInteger power (exponentiation by squaring). Negative exponents truncate toward zero: bases 1 / -1 keep their reciprocal, anything else gives 0.
pow(q, e)(Fraction, Int) → FractionExact rational power. Negative exponents invert the base: pow(3/4, -2) is 16/9.
pow(z, w)(Complex, Complex) → ComplexComplex power.
hypot(x, y)(Float, Float) → FloatHypotenuse: sqrt(x² + y²) without overflow.
remainder(x, y)(Float, Float) → FloatIEEE remainder of x / y. Result is x − n*y where n is the nearest integer to x/y.
16.0 sqrt            -- 4.0
27.0 cbrt            -- 3.0
pow(2.0, 10.0)       -- 1024.0
hypot(3.0, 4.0)      -- 5.0
remainder(5.5, 2.0)  -- -0.5
↑ Back to top

Exponential & Logarithmic Functions

FunctionSignatureDescription
exp(x)Float → Floatex
exp(z)Complex → ComplexComplex exponential.
exp2(x)Float → Float2x
exp10(x)Float → Float10x
expm1(x)Float → Floatex − 1, accurate for small x.
log(x)Float → FloatNatural logarithm (base e).
log(z)Complex → ComplexComplex natural logarithm.
log2(x)Float → FloatBase-2 logarithm.
log10(x)Float → FloatBase-10 logarithm.
log1p(x)Float → Floatlog(1 + x), accurate for small x.
1.0 exp       -- 2.718281828...
1.0 exp log   -- 1.0
1024.0 log2   -- 10.0
1000.0 log10  -- 3.0
↑ Back to top

Trigonometric Functions

Standard trigonometric functions operate in radians. The sinpi, cospi, and tanpi variants compute sin(πx), cos(πx), and tan(πx) with better accuracy at exact multiples.

Standard (radians)

FunctionSignatureDescription
sin(x)Float → FloatSine.
sin(z)Complex → ComplexComplex sine.
cos(x)Float → FloatCosine.
cos(z)Complex → ComplexComplex cosine.
tan(x)Float → FloatTangent.
tan(z)Complex → ComplexComplex tangent.

Inverse trigonometric

FunctionSignatureDescription
asin(x)Float → FloatArcsine. Result in [−π/2, π/2].
asin(z)Complex → ComplexComplex arcsine.
acos(x)Float → FloatArccosine. Result in [0, π].
acos(z)Complex → ComplexComplex arccosine.
atan(x)Float → FloatArctangent. Result in [−π/2, π/2].
atan(z)Complex → ComplexComplex arctangent.
atan2(y, x)(Float, Float) → FloatTwo-argument arctangent. Result in [−π, π].

Pi-scaled

FunctionSignatureDescription
sinpi(x)Float → Floatsin(π · x). Exact at integer values.
cospi(x)Float → Floatcos(π · x). Exact at half-integer values.
tanpi(x)Float → Floattan(π · x).
0.0 sin        -- 0.0
1.0 sinpi      -- 0.0  (exact)
0.5 cospi      -- 0.0  (exact)
atan2(1.0, 1.0) -- 0.7853... (pi/4)
↑ Back to top

Hyperbolic Functions

FunctionSignatureDescription
sinh(x)Float → FloatHyperbolic sine.
sinh(z)Complex → ComplexComplex hyperbolic sine.
cosh(x)Float → FloatHyperbolic cosine.
cosh(z)Complex → ComplexComplex hyperbolic cosine.
tanh(x)Float → FloatHyperbolic tangent.
tanh(z)Complex → ComplexComplex hyperbolic tangent.
asinh(x)Float → FloatInverse hyperbolic sine.
asinh(z)Complex → ComplexComplex inverse hyperbolic sine.
acosh(x)Float → FloatInverse hyperbolic cosine.
acosh(z)Complex → ComplexComplex inverse hyperbolic cosine.
atanh(x)Float → FloatInverse hyperbolic tangent.
atanh(z)Complex → ComplexComplex inverse hyperbolic tangent.
↑ Back to top

Special Functions

FunctionSignatureDescription
erf(x)Float → FloatError function.
erfc(x)Float → FloatComplementary error function: 1 − erf(x).
tgamma(x)Float → FloatGamma function: Γ(x).
lgamma(x)Float → FloatNatural log of the absolute value of Γ(x).
5.0 tgamma     -- 24.0  (i.e. 4!)
0.0 erf        -- 0.0
↑ Back to top

Float Utilities

Manipulation

FunctionSignatureDescription
copysign(x, y)(Float, Float) → FloatReturns x with the sign of y.
nextafter(x, y)(Float, Float) → FloatNext representable float from x toward y.

Predicates

FunctionSignatureDescription
isNan(x)Float → Booltrue if x is NaN (not a number).
isInf(x)Float → Booltrue if x is positive or negative infinity.
isFinite(x)Float → Booltrue if x is neither NaN nor infinity.
isNormal(x)Float → Booltrue if x is a normal floating-point number (not zero, subnormal, infinite, or NaN).
isNan(0.0 / 0.0)   -- true
isInf(1.0 / 0.0)   -- true
42.0 isFinite      -- true
↑ Back to top

Bit Manipulation Functions

These functions operate on Int values, treating them as 64-bit unsigned quantities where applicable.

Bit counting

FunctionSignatureDescription
clz(x)Int → IntCount leading zeros. Returns 64 if x is 0.
clo(x)Int → IntCount leading ones.
ctz(x)Int → IntCount trailing zeros. Returns 64 if x is 0.
cto(x)Int → IntCount trailing ones.
popCount(x)Int → IntCount the number of set (1) bits.

Bit rotation

FunctionSignatureDescription
rotl(x, n)(Int, Int) → IntRotate bits left by n positions.
rotr(x, n)(Int, Int) → IntRotate bits right by n positions.

Power-of-two utilities

FunctionSignatureDescription
bitCeil(x)Int → IntSmallest power of 2 ≥ x.
bitFloor(x)Int → IntLargest power of 2 ≤ x.
bitWidth(x)Int → IntNumber of bits needed to represent x.
hasSingleBit(x)Int → Booltrue if exactly one bit is set (i.e., x is a power of 2).
1 clz             -- 63
0xFF popCount     -- 8
5 bitCeil         -- 8
5 bitFloor        -- 4
8 hasSingleBit    -- true
6 hasSingleBit    -- false
↑ Back to top

Complex Number Functions

Functions for constructing and decomposing complex numbers. Many standard math functions (sqrt, sin, cos, exp, log, pow, etc.) also have Complex overloads listed in their respective sections above.

Complex(real, imag)

Construct a complex number from real and imaginary parts. Both arguments are promoted to Float.

SignatureDescription
Complex(Numeric, Numeric) → ComplexCreate a complex number. Both arguments are converted to Float.
Complex(3.0, 4.0)    -- 3.0+4.0i
Complex(1, 0)        -- 1.0+0.0i

Decomposition & Properties

FunctionSignatureDescription
real(z)Complex → FloatExtract the real part.
imag(z)Complex → FloatExtract the imaginary part.
arg(z)Complex → FloatPhase angle (argument) in radians. Result in [−π, π].
norm(z)Complex → FloatSquared magnitude: real² + imag².
conj(z)Complex → ComplexComplex conjugate: if z = a+bi, then conj(z) = a-bi.
polar(r, θ)(Float, Float) → ComplexCreate complex from polar coordinates: r · (cosθ + i·sinθ).
let z = 3.0 + 4.0i
z real      -- 3.0
z imag      -- 4.0
z abs       -- 5.0
z norm      -- 25.0
z conj      -- 3.0-4.0i
z arg       -- 0.9272... (radians)
polar(5.0, 0.0)  -- 5.0+0.0i
↑ Back to top

Fraction Functions

A Fraction is an exact rational number written with the / operator between two integers, e.g. 7/2. Fractions are always stored in lowest terms with a positive denominator; any sign is carried by the numerator (6/(-8) becomes -3/4). Fractions sit between Int and Float in the numeric tower, so they participate in the usual implicit conversions and arithmetic (see Type Conversions for toFraction, toFloat, etc.).

Numerator & Denominator

FunctionSignatureDescription
numer(x)Fraction → IntNumerator of the reduced fraction. Carries the sign of the value.
denom(x)Fraction → IntDenominator of the reduced fraction. Always positive.
numer(7/2) println;        -- 7
denom(7/2) println;        -- 2

-- Values are reduced to lowest terms first
numer(4/6) println;        -- 2
denom(4/6) println;        -- 3

-- The sign lives in the numerator; the denominator stays positive
numer(6/(-8)) println;    -- -3
denom(6/(-8)) println;    -- 4

-- An integer-valued fraction reduces to denominator 1
denom(10/5) println;       -- 1
↑ Back to top

String Functions

Functions that operate on String values. Strings store UTF-8 bytes. Comparisons and cmp use lexicographic (byte-wise) ordering.

Size & Comparison

FunctionSignatureDescription
length(s)String → IntNumber of bytes in the string.
isEmpty(s)String → BoolTrue when the string has no bytes.
min(a, b)(String, String) → StringLexicographically smaller string.
max(a, b)(String, String) → StringLexicographically larger string.
cmp(a, b)(String, String) → IntReturns −1, 0, or 1.
"hello" length            -- 5
"" length                 -- 0
min("apple", "banana")   -- "apple"
max("apple", "banana")   -- "banana"
cmp("apple", "banana")   -- -1
"abc" < "abd"            -- true

substring(s, start, len)

Returns a substring starting at byte offset start with at most len bytes.

SignatureDescription
substring(String, Int, Int) → StringExtract a substring by byte position and length.
substring("hello world", 0, 5)    -- "hello"
substring("hello world", 6, 5)    -- "world"
substring("abcdef", 2, 3)         -- "cde"

contains(s, sub)

Returns true if the string contains the given substring.

SignatureDescription
contains(String, String) → BoolTest whether sub appears anywhere in s.
contains("hello world", "world")  -- true
contains("hello world", "xyz")    -- false
contains("abc", "")               -- true

startsWith(s, prefix)

Returns true if the string begins with prefix.

SignatureDescription
startsWith(String, String) → BoolTest for a prefix match.
startsWith("hello world", "hello")  -- true
startsWith("hello world", "world")  -- false

endsWith(s, suffix)

Returns true if the string ends with suffix.

SignatureDescription
endsWith(String, String) → BoolTest for a suffix match.
endsWith("hello world", "world")  -- true
endsWith("hello world", "hello")  -- false

split(s, delimiter)

Splits a string by the given delimiter and returns an array of substrings. An empty delimiter splits into individual bytes.

SignatureDescription
split(String, String) → [String]Split string by delimiter.
split("a,b,c", ",")             -- ["a", "b", "c"]
split("one--two--three", "--")  -- ["one", "two", "three"]
split("hello", "")              -- ["h", "e", "l", "l", "o"]

trim(s)

Returns a new string with leading and trailing whitespace removed.

SignatureDescription
trim(String) → StringStrip leading and trailing whitespace.
trim("  hello  ")    -- "hello"
trim("  leading")    -- "leading"
trim("trailing  ")   -- "trailing"

toUpper(s) / toLower(s)

Returns a new string with all ASCII letters converted to upper or lower case.

SignatureDescription
toUpper(String) → StringConvert to upper case.
toLower(String) → StringConvert to lower case.
toUpper("hello")        -- "HELLO"
toLower("HELLO")        -- "hello"
toUpper("Hello World")  -- "HELLO WORLD"
toLower("Hello World")  -- "hello world"

replace(s, from, to)

Returns a new string with all occurrences of from replaced by to.

SignatureDescription
replace(String, String, String) → StringReplace all occurrences of a substring.
replace("hello world", "world", "there")  -- "hello there"
replace("aabbcc", "bb", "XX")              -- "aaXXcc"
replace("abcabc", "abc", "x")              -- "xx"

indexOf(s, needle) / lastIndexOf(s, needle)

Find the first (or last) occurrence of needle in s. The result is a byte offset, so it composes directly with substring's byte indexing. Returns Option.none when the needle does not occur.

SignatureDescription
indexOf(String, String) → Option[Int]Byte offset of the first occurrence.
lastIndexOf(String, String) → Option[Int]Byte offset of the last occurrence.
indexOf("hello world", "world") unwrap     -- 6
indexOf("hello", "xyz") isNone             -- true
lastIndexOf("abcabc", "bc") unwrap        -- 4

-- Composes with substring (both are byte-based)
let s = "key=value";
let i = s indexOf("=") unwrap;
s substring(i + 1, s length - i - 1)      -- "value"

parseInt(s) / parseInt(s, radix) / parseFloat(s)

Strict string-to-number parsing that can report failure (unlike the toInt/toFloat conversions, which only accept values that are already numbers). The whole string must be a valid number — no surrounding whitespace, no trailing characters. Returns Option.none on any malformed input, including integer overflow.

SignatureDescription
parseInt(String) → Option[Int]Parse a base-10 integer with optional sign.
parseInt(String, Int) → Option[Int]Parse in the given radix (2–36; digits beyond 9 are letters, case-insensitive).
parseFloat(String) → Option[Float]Parse a float; "inf"/"nan" and exponents accepted.
parseInt("42") unwrap              -- 42
parseInt("-17") unwrap             -- -17
parseInt("12x") isNone             -- true  (trailing junk)
parseInt(" 12") isNone             -- true  (whitespace not skipped)
parseInt("ff", 16) unwrap          -- 255
parseInt("101", 2) unwrap          -- 5
parseFloat("3.5") unwrap           -- 3.5
parseFloat("1e3") unwrap           -- 1000.0
parseFloat("nope") unwrapOr(0.0)  -- 0.0

codePoints(s)

Returns a lazy List[Int] of Unicode code points decoded from the UTF-8 string. Each element is the integer value of a single code point. Because the result is a lazy list, it composes naturally with take, drop, map, filter, and other list operations without decoding the entire string up front.

SignatureDescription
codePoints(String) → List[Int]Lazy list of Unicode code points.
codePoints("ABC") collect(3)        -- [65, 66, 67]
codePoints("") isNil                -- true
codePoints("Z") head                -- 90
codePoints("\u00e9") head           -- 233   (U+00E9, 2-byte UTF-8)
codePoints("\u20ac") head           -- 8364  (U+20AC, 3-byte UTF-8)
codePoints("hello") take(3) collect(3)  -- [104, 101, 108]

Byte Indexing

Strings support the [] indexing operator to access individual bytes. The index is zero-based and the result is an Int representing the byte value (0–255).

ExpressionTypeDescription
s[i]IntByte value at index i.
let s = "hello";
s[0]   -- 104  (ASCII 'h')
s[1]   -- 101  (ASCII 'e')
s[4]   -- 111  (ASCII 'o')

toSymbol(s)

Intern a string into a Symbol. The resulting symbol is the same interned value as the corresponding '-literal, so toSymbol("foo") == 'foo. Use this when the symbol name is only known at runtime (built from string operations) rather than written as a literal.

SignatureDescription
toSymbol(String) → SymbolIntern the string's text as a symbol.
toSymbol("hello") println;          -- hello
toSymbol("foo") == 'foo;              -- true  (same interned symbol)
toSymbol("note_" $ toString(3));    -- 'note_3  (name built at runtime)
↑ Back to top

String Formatting

The fmt function formats a string by substituting placeholders with values. It takes variadic arguments — any number of values of any type.

fmt(template, ...values)

SignatureDescription
fmt(String, ...args) → StringFormat a string by replacing placeholders with the given values.

Placeholders

PlaceholderDescription
%^Positional — replaced by the next value in order (left to right).
%0%9Indexed — replaced by the value at the given zero-based index. Can be repeated.
%%Literal percent sign.
-- Positional placeholders
"%^ + %^ = %^" fmt(1, 2, 3)         -- "1 + 2 = 3"

-- Indexed placeholders (zero-based)
"%0 and %2 and %1" fmt("a", "b", "c")  -- "a and c and b"

-- Repeated index
"%0 %0 %0" fmt("echo")              -- "echo echo echo"

-- Escape percent
"100%% done" fmt()                   -- "100% done"
"%%=%^" fmt(42)                      -- "%=42"

-- Works with any type
"half = %^" fmt(1/2)                 -- "half = 1/2"
"array: %^" fmt([10, 20, 30])        -- "array: [10, 20, 30]"
"list: %^" fmt(1::2::3::nil)         -- "list: List(1, 2, 3)"

-- Pipeline style (idiomatic)
"hello %^!" fmt("world") println;    -- hello world!
"%^ items" fmt(42) println;          -- 42 items

-- Zero args when no placeholders
"no placeholders" fmt()              -- "no placeholders"
↑ Back to top

Range Functions

Functions that operate on Range<Int> values.

FunctionSignatureDescription
length(r)Range<Int> → IntNumber of elements in the range. Returns -1 for infinite ranges.
isEmpty(r)Range<Int> → BoolTrue when the range has no elements. Infinite ranges are never empty.
toArray(r)Range<Int> → [Int]Materialize the range into an array.
toList(r)Range<Int> → List<Int>Create a lazy list from the range. Elements are generated on demand.
let r = (1..5);
r length     -- 5
r toArray    -- [1, 2, 3, 4, 5]
r toList     -- List(1, 2, 3, 4, 5) (lazy)
↑ Back to top

Collection Functions (Arrays & Lists)

These functions are overloaded to work on both [T] and List<T>. Array versions are eager (return a new array); list versions are lazy (return a lazily-evaluated list). All functions support pipeline syntax: xs map(f) is equivalent to map(xs, f).

Pipeline syntax: All collection functions can be called in pipeline style. xs take(3) map(f) filter(g) is equivalent to filter(map(take(xs, 3), f), g).

Size

length(collection)

Returns the number of elements in the collection.

SignatureDescription
length([T]) → IntNumber of elements in the array.
length(List<T>) → IntNumber of elements in the list. Forces the entire list.
Warning: length on a List forces every element. Calling it on an infinite list (e.g. from cyc or a range like (1..)) will never terminate.
[1, 2, 3, 4, 5] length             -- 5
List(1, 2, 3) length               -- 3
[10, 20, 30] length                -- 3

isEmpty(collection)

Returns true when the collection has no elements. Defined for every collection length covers — arrays, lists, maps, sets, strings, ranges, and persistent vectors/maps. Unlike length, isEmpty on a List answers in O(1) without forcing any elements (equivalent to isNil), so it is safe on infinite lists. An infinite range is never empty.

SignatureDescription
isEmpty([T]) → BoolTrue when the array has no elements.
isEmpty(List<T>) → BoolTrue when the list is nil. O(1); does not force.
[Int]() isEmpty                    -- true
[1, 2, 3] isEmpty                  -- false
nil isEmpty                        -- true
(1..) isEmpty                      -- false (infinite, does not hang)

Slicing & Selection

take(collection, n)

Returns the first n elements.

SignatureDescription
take([T], Int) → [T]First n elements of an array.
take(List<T>, Int) → List<T>Lazily takes first n elements of a list.
[1, 2, 3, 4, 5] take(3)            -- [1, 2, 3]
List(1, 2, 3, 4, 5) take(3)       -- List(1, 2, 3)

drop(collection, n)

Skips the first n elements and returns the rest.

SignatureDescription
drop([T], Int) → [T]All but the first n elements of an array.
drop(List<T>, Int) → List<T>Eagerly skips n elements, returns the rest of the list.
[1, 2, 3, 4, 5] drop(2)            -- [3, 4, 5]
List(1, 2, 3, 4, 5) drop(2)       -- List(3, 4, 5)

takeWhile(collection, predicate)

Returns elements from the front while the predicate holds.

SignatureDescription
takeWhile([T], (T) → Bool) → [T]Eagerly takes elements while predicate is true.
takeWhile(List<T>, (T) → Bool) → List<T>Lazily takes elements while predicate is true.
[1, 2, 3, 4, 5] takeWhile(fn(x Int) { x < 4 })     -- [1, 2, 3]

dropWhile(collection, predicate)

Drops elements from the front while the predicate holds, returns the rest.

SignatureDescription
dropWhile([T], (T) → Bool) → [T]Eagerly drops elements while predicate is true.
dropWhile(List<T>, (T) → Bool) → List<T>Lazily drops elements while predicate is true.
[1, 2, 3, 4, 5] dropWhile(fn(x Int) { x < 4 })     -- [4, 5]

stride(collection, n)

Takes every nth element, starting from the first.

SignatureDescription
stride([T], Int) → [T]Every nth element of an array.
stride(List<T>, Int) → List<T>Lazily takes every nth element of a list.
[1, 2, 3, 4, 5, 6, 7] stride(2)            -- [1, 3, 5, 7]
[1, 2, 3, 4, 5, 6, 7, 8, 9] stride(3)     -- [1, 4, 7]

stutter(collection, n), stutter(array, counts)

Repeats each element n times. The counts overload replicates element i counts[i] times; the counts array is indexed cyclically (like ordinary array indexing), so a shorter counts array tiles across the source, and counts <= 0 drop the element.

SignatureDescription
stutter([T], Int) → [T]Repeat each element n times in an array.
stutter(List<T>, Int) → List<T>Lazily repeats each element n times.
stutter([T], [Int]) → [T]Per-element replication counts.
[1, 2, 3] stutter(2)            -- [1, 1, 2, 2, 3, 3]
[10, 20, 30] stutter([1, 0, 2]) -- [10, 30, 30]
[7, 8] stutter([3])             -- [7, 7, 7, 8, 8, 8]

repeat(value, n)

Creates an array containing value repeated n times.

SignatureDescription
repeat(T, Int) → [T]Returns an array of n copies of the value. Works with any type.
repeat(0, 5)                    -- [0, 0, 0, 0, 0]
repeat(3.14, 3)                 -- [3.14, 3.14, 3.14]
repeat("hello", 2)              -- [hello, hello]
repeat(true, 4)                 -- [true, true, true, true]
repeat(42, 0)                   -- []

Transformation

map(collection, fn)

Applies a function to each element, returning a new collection of the results.

SignatureDescription
map([T], (T) → U) → [U]Eagerly maps over an array.
map(List<T>, (T) → U) → List<U>Lazily maps over a list.
[1, 2, 3] map(fn(x Int) { x * x })         -- [1, 4, 9]
[1, 2, 3] map(fn(x Int) { x * 2 })         -- [2, 4, 6]
List(1, 2, 3) map(fn(x Int) { x + 10 })    -- List(11, 12, 13)

filter(collection, predicate)

Returns elements that satisfy the predicate.

SignatureDescription
filter([T], (T) → Bool) → [T]Eagerly filters an array.
filter(List<T>, (T) → Bool) → List<T>Lazily filters a list.
[1, 2, 3, 4, 5] filter(fn(x Int) { x > 2 })     -- [3, 4, 5]
[1, 2, 3, 4, 5] filter(fn(x Int) { x % 2 == 0 })  -- [2, 4]

zip(a, b)

Combines two collections element-wise into a collection of tuples. Result length is the shorter of the two inputs.

SignatureDescription
zip([T], [U]) → [(T, U)]Eagerly zips two arrays into an array of tuples.
zip(List<T>, List<U>) → List<(T, U)>Lazily zips two lists into a list of tuples.
zip([1, 2, 3], [10, 20, 30])     -- [(1, 10), (2, 20), (3, 30)]
zip(["a", "b"], [1, 2])         -- [("a", 1), ("b", 2)]

enumerate(collection)

Pairs each element with its zero-based index.

SignatureDescription
enumerate([T]) → [(Int, T)]Eagerly enumerates an array.
enumerate(List<T>) → List<(Int, T)>Lazily enumerates a list.
["a", "b", "c"] enumerate      -- [(0, "a"), (1, "b"), (2, "c")]
[10, 20, 30] enumerate         -- [(0, 10), (1, 20), (2, 30)]

Folding & Scanning

fold(collection, init, fn)

Left fold (reduce) with an initial accumulator value.

SignatureDescription
fold([T], U, (U, T) → U) → UFold over an array.
fold(List<T>, U, (U, T) → U) → UFold over a list (eager — forces the entire list).
[1, 2, 3, 4, 5] fold(0, fn(acc Int, x Int) { acc + x })    -- 15
["a", "b", "c"] fold("", fn(s String, x String) { s $ x })  -- "abc"

scan(collection, init, fn)

Like fold, but returns all intermediate accumulator values. The result has one more element than the input (starts with init).

SignatureDescription
scan([T], U, (U, T) → U) → [U]Eagerly scans an array.
scan(List<T>, U, (U, T) → U) → List<U>Lazily scans a list.
[1, 2, 3, 4, 5] scan(0, fn(a Int, x Int) { a + x })
    -- [0, 1, 3, 6, 10, 15]  (running sum)

fold1(collection, fn)

Left fold using the first element as the initial accumulator. The collection must be non-empty.

SignatureDescription
fold1([T], (T, T) → T) → TFold over an array using first element as init.
fold1(List<T>, (T, T) → T) → TFold over a list using first element as init.
[1, 2, 3, 4, 5] fold1(fn(a Int, b Int) { a + b })     -- 15
[3, 1, 4, 1, 5] fold1(fn(a Int, b Int) { max(a, b) })   -- 5

scan1(collection, fn)

Like fold1, but returns all intermediate accumulator values. The result has the same length as the input.

SignatureDescription
scan1([T], (T, T) → T) → [T]Eagerly scans an array.
scan1(List<T>, (T, T) → T) → List<T>Lazily scans a list.
[1, 2, 3, 4, 5] scan1(fn(a Int, b Int) { a + b })
    -- [1, 3, 6, 10, 15]  (running sum without init)

find(collection, predicate)

Returns the index of the first element satisfying the predicate, or -1 if not found.

SignatureDescription
find([T], (T) → Bool) → IntIndex of first match in an array.
find(List<T>, (T) → Bool) → IntIndex of first match in a list (forces elements until found).
[10, 20, 30, 40] find(fn(x Int) { x > 25 })     -- 2  (index of 30)
[1, 2, 3] find(fn(x Int) { x > 10 })            -- -1  (not found)

Combining

cat(a, b)

Concatenates two collections of the same type.

SignatureDescription
cat([T], [T]) → [T]Concatenate two arrays.
cat(List<T>, List<T>) → List<T>Lazily concatenate two lists.
cat([1, 2, 3], [4, 5, 6])                -- [1, 2, 3, 4, 5, 6]
cat(List(1, 2), List(3, 4))              -- List(1, 2, 3, 4)

join(collection)

Flattens one level of nesting. Given a collection of collections, returns a single flat collection. For String elements — strings being sequences of characters — one-level flattening means concatenation; a separator overload exists for the array form as a convenience. The String forms only apply when the element type is String, so they never hide a nested-collection flatten.

SignatureDescription
join([[T]]) → [T]Flatten an array of arrays.
join(List<List<T>>) → List<T>Lazily flatten a list of lists.
join([String]) → String, join(List<String>) → StringConcatenate strings.
join([String], String) → String, join(List<String>, String) → StringConcatenate with a separator between elements.
[[1, 2], [3, 4], [5]] join             -- [1, 2, 3, 4, 5]
List(List(1, 2), List(3, 4)) join      -- List(1, 2, 3, 4)
["ab", "cd"] join                      -- "abcd"
["a", "b"] join(", ")                  -- "a, b"

flatten(collection)

Recursively flattens all nesting levels of the same container type. Unlike join which removes only one level, flatten removes all levels of Array (or List) nesting until the element type is no longer the same container type.

SignatureDescription
flatten([[...[T]...]]) → [T]Recursively flatten all Array nesting levels.
flatten(List<..List<T>..>) → List<T>Lazily flatten all List nesting levels.
[[1, 2], [3, 4]] flatten                        -- [1, 2, 3, 4]  (same as join)
[[[1, 2], [3]], [[4, 5, 6]]] flatten            -- [1, 2, 3, 4, 5, 6]  (all levels)
[[[List(1, 2)], [List(3)]]] flatten             -- [List(1, 2), List(3)]  (stops at List)
flatten only removes layers of the same container type. A [[List<Int>]] flattens to [List<Int>] — the array layers are removed but the List is preserved.

sum(collection), product(collection), mean(collection)

Numeric reductions over Int, Float, Fraction, or Complex elements; the result has the element type. sum of an empty collection is 0, product is 1, and mean is nan. mean is Int/Float only and always returns Float.

SignatureDescription
sum([T]) → T for T in Int/Float/Fraction/ComplexSum of the elements. Also for the List forms.
product([T]) → T, same element typesProduct of the elements. Also for lists.
mean([Int]) → Float, mean([Float]) → FloatArithmetic mean. Also for lists.
[1, 2, 3, 4] sum                         -- 10
[1/2, 1/3, 1/6] sum                      -- 1/1
(1..5) toList product                    -- 120
[1.0, 2.0, 6.0] mean                     -- 3.0

min(collection), max(collection)

Smallest / largest element of an array or list. Works for Int, Float, Fraction, and String elements (the two-arg scalar forms min(a, b) / max(a, b) are documented in the math section). Empty collections yield the reduction identity: INT64_MAX/INT64_MIN for Int and Fraction, ±inf for Float, "" for String.

SignatureDescription
min([T]) → T for T in Int/Float/Fraction/StringMinimum element. Also for lists.
max([T]) → T, same element typesMaximum element. Also for lists.
[3, 1, 4, 1, 5] min                      -- 1
[3, 1, 4, 1, 5] max                      -- 5
["banana", "apple"] min                  -- "apple"

sums(collection), products(collection), mins(collection), maxs(collection)

Running (cumulative) reductions: element i of the result is the reduction of elements 0..i, so the result has the same length as the input. The List forms are lazy and compose with infinite lists.

SignatureDescription
sums([T]) → [T] for T in Int/Float/Fraction/ComplexRunning sum. Also for lists (lazy).
products(...)Running product; same shapes.
mins(...), maxs(...)Running minimum / maximum; Int/Float/Fraction (Complex is unordered).
[1, 2, 3, 4] sums                        -- [1, 3, 6, 10]
[3, 1, 4, 1, 5] mins                     -- [3, 1, 1, 1, 1]
(1..) toList sums take(5)                -- List(1, 3, 6, 10, 15)

any(collection, fn), all(collection, fn)

Predicate quantifiers, short-circuiting (lazy lists are only forced as far as needed). any of an empty collection is false; all is true. Without a predicate they reduce a Bool collection directly.

SignatureDescription
any([T], (T) Bool) → BoolTrue if the predicate holds for some element. Also for lists.
all([T], (T) Bool) → BoolTrue if the predicate holds for every element. Also for lists.
any([Bool]) → Bool, all([Bool]) → BoolReduce a Bool collection by or / and. Also for lists.
[1, 2, 3] any(fn(x Int) { x > 2 })       -- true
[1, 2, 3] all(fn(x Int) { x > 2 })       -- false
[true, false] any                        -- true

contains(collection, x)

Membership test for arrays and lists (in addition to the String/Set/Map forms documented in their own sections). Uses element equality, so it works for strings, tuples, and other composite element types.

SignatureDescription
contains([T], T) → BoolTrue if some element equals x.
contains(List<T>, T) → BoolList form; forces lazy elements only until a match.
[1, 2, 3] contains(2)                    -- true
List((1, 2), (3, 4)) contains((3, 4))    -- true

clump(array, n)

Groups an array into rows of n elements; a short remainder row is kept. n <= 0 yields an empty array.

SignatureDescription
clump([T], Int) → [[T]]Fixed-size grouping.
(1..10) toArray clump(3)                 -- [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]

toSet(collection)

Builds a Set from the elements of an array or list, discarding duplicates.

SignatureDescription
toSet([T]) → Set<T>Array form.
toSet(List<T>) → Set<T>List form (forces the whole list).
[1, 2, 2, 3, 1] toSet length             -- 3

fromCodePoints(collection)

The inverse of codePoints: builds a String from Unicode code points (UTF-8 encoded). Invalid code points become U+FFFD.

SignatureDescription
fromCodePoints([Int]) → StringArray form.
fromCodePoints(List<Int>) → StringList form.
[72, 105, 33] fromCodePoints             -- "Hi!"
"héllo" codePoints fromCodePoints        -- "héllo"
↑ Back to top

Array-only Functions

These functions operate exclusively on [T]. Functions ending in ! mutate the array in place; functions without ! return a new array and leave the input untouched.

reverse(array)

Returns a new array with elements in reverse order.

SignatureDescription
reverse([T]) → [T]Reverse an array (returns a new array).
[1, 2, 3, 4, 5] reverse         -- [5, 4, 3, 2, 1]
[1, 2, 3] reverse               -- [3, 2, 1]

push(array, element)

Returns a new array with the element appended at the end. The original array is unchanged.

SignatureDescription
push([T], T) → [T]Append an element to an array (returns a new array).
[1, 2, 3] push(4)               -- [1, 2, 3, 4]

pop(array)

Returns a new array with the last element removed. The original array is unchanged.

SignatureDescription
pop([T]) → [T]Remove the last element of an array (returns a new array).
[1, 2, 3, 4] pop                -- [1, 2, 3]
[1, 2, 3] pop                   -- [1, 2]

Indexed assignment a[i] = x

In-place write to a single array slot. Indices wrap cyclically — a[-1] = x writes the last element, a[n] = x when n ≥ length wraps via i mod length. This matches the existing read semantics of a[i]. Writing through any alias affects all aliases of the same array.

var a = [1, 2, 3, 4, 5];
a[0] = 100;
a[-1] = 999;
a println;                       -- [100, 2, 3, 4, 999]

at(array, index)

Element read as an ordinary function — a at(i) is exactly a[i], cyclic index included. With an array of indices it gathers, mirroring a[[i1, i2, …]]. Together with put! below, this lets generic code use one protocol over arrays and user indexable types (see Indexable Objects in Tzopilotl by Example).

SignatureDescription
at([T], Int) → TElement at cyclic index (negative wraps from the end).
at([T], [Int]) → [T]Gather: new array of the elements at each index.
let a = [10, 20, 30];
a at(1) println;                 -- 20
a at(-1) println;                -- 30
a at([2, 0, 0]) println;         -- [30, 10, 10]

put!(array, index, value)

Element write as an ordinary function — a put!(i, v) is exactly a[i] = v, cyclic index included. Returns the same array for chaining.

SignatureDescription
put!([T], Int, T) → [T]Write element at cyclic index in place. Returns the same array.
var a = [1, 2, 3];
a put!(1, 99);
a put!(-1, 30) println;          -- [1, 99, 30]

push!(array, element)

Mutating append. Adds the element to the end of this array in place and returns the array itself, allowing chained calls.

SignatureDescription
push!([T], T) → [T]Append an element in place. Returns the same array.
var a = [1, 2, 3];
a push!(4);
a push!(5) push!(6);
a println;                       -- [1, 2, 3, 4, 5, 6]

pop!(array)

Mutating pop. Removes the last element from the array in place and returns it. Behavior on an empty array is undefined.

SignatureDescription
pop!([T]) → TRemove and return the last element (mutates the array).
var a = [1, 2, 3, 4];
let last = a pop!;
last println;                    -- 4
a println;                       -- [1, 2, 3]

clear!(array)

Mutating clear. Removes every element in place. Returns the same array for chaining. Also defined for maps and sets (see the Map and Set sections).

SignatureDescription
clear!([T]) → [T]Remove every element (mutates the array). Returns the same array.
var a = [1, 2, 3];
a clear!;
a println;                       -- []

append!(array, source)

Mutating bulk append — the in-place analogue of $ concatenation. Appends every element of the source (an array or a list of the same element type) to the array. Returns the same array for chaining. A list source is forced as it is walked, so an infinite list will not terminate. Self-append (a append!(a)) is safe and doubles the array.

SignatureDescription
append!([T], [T]) → [T]Append every element of the source array (mutates the first array). Returns the same array.
append!([T], List<T>) → [T]Append every element of the list, forcing it.
var a = [1, 2];
a append!([3, 4]);
a println;                       -- [1, 2, 3, 4]
a append!(List(5, 6));
a println;                       -- [1, 2, 3, 4, 5, 6]
a clear! append!([9]);           -- replace contents in place
a println;                       -- [9]

sort(array)

Returns a new array sorted in ascending order.

SignatureDescription
sort([Int]) → [Int]Sort integers ascending.
sort([Float]) → [Float]Sort floats ascending.
sort([String]) → [String]Sort strings lexicographically.
[3, 1, 4, 1, 5, 9, 2, 6] sort     -- [1, 1, 2, 3, 4, 5, 6, 9]
[5, 3, 1, 4, 2] sort              -- [1, 2, 3, 4, 5]

muss(array)

Returns a new array with elements in a scrambled (pseudo-random) order. Uses a deterministic shuffle based on array content.

SignatureDescription
muss([T]) → [T]Scramble an array.
[1, 2, 3, 4, 5] muss     -- e.g. [3, 5, 1, 4, 2]

copy(array)

Returns a shallow copy of the array. Use this when you need to mutate one alias without affecting another — arrays are passed by reference, so plain assignment (var b = a;) does not create an independent copy. Nested Obj* elements are shared, not deep-copied.

SignatureDescription
copy([T]) → [T]Shallow copy of an array.
var a = [1, 2, 3];
var b = a copy;
a push!(4);
a println;                       -- [1, 2, 3, 4]
b println;                       -- [1, 2, 3]   (b is independent)
↑ Back to top

List-only Functions

These functions operate exclusively on List<T>.

head(list)

Returns the first element of the list. Behavior is undefined on a nil list.

SignatureDescription
head(List<T>) → TFirst element of the list.
List(1, 2, 3) head     -- 1
List(42) head          -- 42

tail(list)

Returns the list without its first element. Returns nil if the list has only one element. Behavior is undefined on a nil list.

SignatureDescription
tail(List<T>) → List<T>All elements after the first.
List(1, 2, 3) tail     -- List(2, 3)
List(42) tail          -- nil

cons(element, list)

Prepends an element to the front of a list, returning a new list. The list may be nil.

SignatureDescription
cons(T, List<T>) → List<T>Prepend an element to a list.
cons(0, List(1, 2, 3))     -- List(0, 1, 2, 3)
let empty List<Int> = nil;
cons(1, empty)             -- List(1)
Note: The :: (cons) operator provides equivalent functionality with infix syntax: 0 :: List(1, 2, 3).

isNil(list) / notNil(list)

Test whether a list is empty (nil) or non-empty.

SignatureDescription
isNil(List<T>) → BoolReturns true if the list is nil.
notNil(List<T>) → BoolReturns true if the list is not nil.
let xs = List(1, 2, 3);
let empty List<Int> = nil;
isNil(xs)       -- false
isNil(empty)    -- true
notNil(xs)      -- true
notNil(empty)   -- false

cyc(list)

Returns an infinite list that cycles through the elements of the input list repeatedly.

SignatureDescription
cyc(List<T>) → List<T>Infinite cycle of a list.
List(1, 2, 3) cyc take(10)     -- List(1, 2, 3, 1, 2, 3, 1, 2, 3, 1)
List(1, 2, 3) cyc take(7)      -- List(1, 2, 3, 1, 2, 3, 1)

ncyc(collection, n)

Returns the input repeated n times. Works on both lists and arrays (unlike cyc, whose infinite result only makes sense as a lazy list).

SignatureDescription
ncyc(List<T>, Int) → List<T>Cycle a list n times.
ncyc([T], Int) → [T]Cycle an array n times.
List(1, 2, 3) ncyc(3)     -- List(1, 2, 3, 1, 2, 3, 1, 2, 3)
[1, 2] ncyc(4)            -- [1, 2, 1, 2, 1, 2, 1, 2]

hang(list)

Returns an infinite list that, upon reaching the last element, repeats it indefinitely.

SignatureDescription
hang(List<T>) → List<T>Hang on the last element forever.
List(1, 2, 3) hang take(8)     -- List(1, 2, 3, 3, 3, 3, 3, 3)
List(10, 20) hang take(5)      -- List(10, 20, 20, 20, 20)

iter(value, fn)

Returns an infinite list of repeated applications of fn to value: value, fn(value), fn(fn(value)), …

SignatureDescription
iter(T, (T) → T) → List<T>Infinite iterated function application.
iter(1, fn(x Int) Int { x * 2 }) take(10)
    -- List(1, 2, 4, 8, 16, 32, 64, 128, 256, 512)
iter(0, fn(x Int) Int { x + 1 }) take(5)
    -- List(0, 1, 2, 3, 4)
↑ Back to top

Map Functions

Functions that operate on [K: V] values. Maps are heap-allocated hash maps passed by reference. The functions without a trailing ! (put, remove, merge, …) return new maps and leave their inputs untouched. The bang-suffixed variants put! and remove! mutate in place; m[key] = value is equivalent to put!. Map literals use [key: value] syntax.

get(map, key) / get(map, key, default)

Retrieves the value for a key. The two-argument form returns Option<V>Option.some(value) if found, Option.none if missing. The three-argument form returns the value directly, using the provided default if the key is missing.

SignatureDescription
get([K: V], K) → Option<V>Get value for key; returns Option.some(v) or Option.none.
get([K: V], K, V) → VGet value for key; returns default if missing.
let m = ["a": 1, "b": 2, "c": 3];
m get("b") println;              -- Option<Int>.some(2)
m get("z") println;              -- Option<Int>.none
m get("b") unwrap println;       -- 2
m get("z", 99) println;          -- 99 (not found, returns default)

Map subscript syntax also returns Option<V>:

m["b"] println;                   -- Option<Int>.some(2)
m["z"] println;                   -- Option<Int>.none
m["b"] unwrap println;            -- 2
m["z"] unwrapOr(99) println;      -- 99

getOrDefault(map, key, default)

Returns the value for the key, or default if the key is missing. Identical in behavior to the three-argument get; provided as a more descriptive name. The default is evaluated eagerly — if computing it is expensive or should only happen on a miss, use getOrElse instead.

SignatureDescription
getOrDefault([K: V], K, V) → VValue for key, or the given default if missing.
let m = ["a": 1, "b": 2];
m getOrDefault("a", 0) println;  -- 1
m getOrDefault("z", 0) println;  -- 0

getOrElse(map, key, fallback)

Returns the value for the key, or the result of calling fallback with the key if it is missing. The fallback is a function (K) → V and runs only on a miss (lazy), so use it when the default depends on the key or is costly to compute.

SignatureDescription
getOrElse([K: V], K, (K) → V) → VValue for key, or fallback(key) if missing (fallback called lazily).
let m = ["a": 1, "b": 2];
m getOrElse("b", fn(k String) Int { 0 }) println;        -- 2 (fallback not called)
m getOrElse("missing", fn(k String) Int { k length }) println;  -- 7

Indexed assignment m[key] = value

Insert-or-update on a map in place. If the key already exists, its value is replaced; otherwise a new entry is added.

var m = ["a": 1];
m["b"] = 2;                      -- insert
m["a"] = 99;                     -- update existing
m println;                       -- [a: 99, b: 2]

put(map, key, value)

Returns a new map with the key-value pair added or updated. The input map is unchanged. For in-place writes, prefer m[key] = value.

SignatureDescription
put([K: V], K, V) → [K: V]Add or update a key-value pair (returns a new map).
let m = ["a": 1];
let m2 = m put("b", 2);
m2 println;                      -- [a: 1, b: 2]

put!(map, key, value)

Adds or updates the key-value pair in place and returns the same map (for chaining). Equivalent to m[key] = value.

SignatureDescription
put!([K: V], K, V) → [K: V]Add or update a key-value pair in place. Returns the same map.
var m = ["a": 1];
m put!("b", 2);
m put!("a", 99) put!("c", 3);    -- chains; mutates m each time
m length println;                -- 3

remove(map, key)

Returns a new map with the given key removed.

SignatureDescription
remove([K: V], K) → [K: V]Remove a key from the map.

remove!(map, key)

Removes the given key in place and returns the same map (for chaining). Removing an absent key is a no-op.

SignatureDescription
remove!([K: V], K) → [K: V]Remove a key in place. Returns the same map.

contains(map, key)

Returns true if the key exists in the map.

SignatureDescription
contains([K: V], K) → BoolCheck if key exists.
let m = ["a": 1, "b": 2];
m contains("a") println;         -- true
m contains("z") println;         -- false

keys(map)

Returns an array of all keys in the map.

SignatureDescription
keys([K: V]) → [K]Get all keys.

values(map)

Returns an array of all values in the map.

SignatureDescription
values([K: V]) → [V]Get all values.
let m = ["x": 10, "y": 20];
m keys println;                  -- ["x", "y"]
m values println;                -- [10, 20]

pairs(map)

Returns an array of (key, value) tuples from the map.

SignatureDescription
pairs([K: V]) → [(K, V)]Get all key-value pairs as tuples.
let m = ["a": 1, "b": 2, "c": 3];
m pairs println;                 -- [(a, 1), (b, 2), (c, 3)]

merge(map1, map2)

Returns a new map containing all key-value pairs from both maps. Values from the second map override those in the first for duplicate keys.

SignatureDescription
merge([K: V], [K: V]) → [K: V]Merge two maps (second wins on conflicts).
let a = ["x": 1, "y": 2];
let b = ["y": 99, "z": 3];
merge(a, b) println;             -- [x: 1, y: 99, z: 3]

mergeIfAbsent(map1, map2)

Returns a new map: a copy of map1 with entries from map2 added only for keys that are not already in map1. Existing keys keep map1's values. Both inputs are unchanged.

SignatureDescription
mergeIfAbsent([K: V], [K: V]) → [K: V]Add only the keys missing from the first map.
let a = ["x": 1, "y": 2];
let b = ["y": 99, "z": 3];
mergeIfAbsent(a, b) println;     -- [x: 1, y: 2, z: 3]

mergeIfPresent(map1, map2)

Returns a new map: a copy of map1 with values overwritten from map2 only for keys that already exist in map1. Keys unique to map2 are ignored. Both inputs are unchanged.

SignatureDescription
mergeIfPresent([K: V], [K: V]) → [K: V]Overwrite only the keys shared with the first map.
let a = ["x": 1, "y": 2];
let b = ["y": 99, "z": 3];
mergeIfPresent(a, b) println;    -- [x: 1, y: 99]

length(map) / isEmpty(map)

Returns the number of key-value pairs in the map, or whether there are none.

SignatureDescription
length([K: V]) → IntNumber of entries in the map.
isEmpty([K: V]) → BoolTrue when the map has no entries.

clear!(map)

Mutating clear. Removes every entry in place. Returns the same map for chaining.

SignatureDescription
clear!([K: V]) → [K: V]Remove every entry (mutates the map). Returns the same map.

copy(map)

Returns a shallow copy of the map. Use when an alias needs to be mutated independently. Nested Obj* keys and values are shared with the original.

SignatureDescription
copy([K: V]) → [K: V]Shallow copy of a map.
↑ Back to top

Option Functions

Functions that operate on Option<T> values. Option<T> is a built-in template enum with cases some T and none. It is returned by map subscript (map[key]) and get(map, key) to safely handle missing keys.

unwrap(option)

Extracts the value from a some. Halts with an error if the option is none.

SignatureDescription
unwrap(Option<T>) → TExtract the value; halts on none.
let m = ["a": 1, "b": 2];
m["a"] unwrap println;            -- 1
-- m["z"] unwrap println;         -- would halt: unwrap called on none

unwrapOr(option, default)

Extracts the value from a some, or returns the default value if none.

SignatureDescription
unwrapOr(Option<T>, T) → TExtract the value or use default.
let m = ["a": 1, "b": 2];
m["a"] unwrapOr(99) println;      -- 1
m["z"] unwrapOr(99) println;      -- 99

isSome(option)

Returns true if the option contains a value.

SignatureDescription
isSome(Option<T>) → BoolCheck if option has a value.
let m = ["a": 1];
m["a"] isSome println;            -- true
m["z"] isSome println;            -- false

isNone(option)

Returns true if the option is empty (no value).

SignatureDescription
isNone(Option<T>) → BoolCheck if option is empty.
let m = ["a": 1];
m["a"] isNone println;            -- false
m["z"] isNone println;            -- true
↑ Back to top

Reference Functions

Functions for creating, reading, and writing mutable references. These are the functional equivalents of the &, *, and <- operators, and unlike operators they participate in automapping.

ref(value)

Creates a new mutable reference containing the given value.

SignatureDescription
ref(T) → Ref<T>Create a mutable reference to a value.
let x = ref(42);             -- Ref<Int>
let y = ref(3.14);           -- Ref<Float>
let s = ref("hello");        -- Ref<String>

-- Automap: create refs from an array
let refs = [1, 2, 3] ref;   -- [Ref<Int>]

deref(ref)

Reads the current value from a mutable reference.

SignatureDescription
deref(Ref<T>) → TRead the value from a reference.
let x = ref(42);
deref(x) println;               -- 42
x deref println;                 -- 42 (postfix)

-- Automap: deref an array of refs
let refs = [ref(1), ref(2), ref(3)];
refs deref println;              -- [1, 2, 3]

setref(value, ref)

Writes a new value into a mutable reference. Returns the written value.

SignatureDescription
setref(T, Ref<T>) → TWrite a value into a reference; returns the value.
let x = ref(0);
setref(42, x);
x deref println;                 -- 42

-- Postfix style
99 setref(x);
x deref println;                 -- 99

-- Automap: set all refs in an array
let refs = [ref(1), ref(2), ref(3)];
setref(0, refs);
refs deref println;              -- [0, 0, 0]
↑ Back to top

Any Functions

Functions for working with the Any type. Any wraps a value of any type into a uniform runtime object, enabling heterogeneous collections and dynamic type dispatch.

any(value)

Wraps a single value of any type into an Any object.

SignatureDescription
any(T) → AnyWrap a value of any type into Any.
any(5) println;              -- Any(5, Int)
any("hello") println;        -- Any(hello, String)
any(3.14) println;           -- Any(3.14, Float)
any(true) println;           -- Any(true, Bool)

any(v1, v2, ...)

Wraps multiple values and returns a [Any].

SignatureDescription
any(T1, T2, ...) → [Any]Wrap each argument into Any and return as an array.
any(1, "two", 3.0) println;
-- [Any(1, Int), Any(two, String), Any(3.0, Float)]

-- Nested calls work
any(1, any(2, 3)) println;
-- [Any(1, Int), Any([Any(2, Int), Any(3, Int)], [Any])]

toAnyArray

Converts a tuple to a [Any], wrapping each element.

SignatureDescription
toAnyArray(Tuple) → [Any]Convert a tuple to a heterogeneous Any array.
(1, "two", 'three, 4.0) toAnyArray println;
-- [Any(1, Int), Any(two, String), Any('three, Symbol), Any(4.0, Float)]

as(Type)

Postfix type-test operator on Any values. Returns Option<T>some(value) if the wrapped type matches, none otherwise.

SignatureDescription
Any as(T) → Option<T>Test and unwrap the Any value to the given type.
let a = any(5);
a as(Int) println;           -- Option<Int>.some(5)
a as(String) println;        -- Option<String>.none
↑ Back to top

Coroutine Functions

Functions for working with coroutines. A coroutine is declared with coro fn and produces a Coroutine<T> value that lazily yields elements of type T. See the Coroutines guide for full language details.

next(coroutine)

Resumes the coroutine and returns the next yielded value wrapped in Option<T>. Returns none when the coroutine is exhausted.

SignatureDescription
next(Coroutine<T>) → Option<T>Resume and get the next value.
coro fn count(n Int) Int {
    var i = 0;
    while (i < n) {
        yield i;
        i = i + 1;
    }
}

let c = count(3);
c next println;     -- Option<Int>.some(0)
c next println;     -- Option<Int>.some(1)
c next println;     -- Option<Int>.some(2)
c next println;     -- Option<Int>.none

yield(value)

Suspends the coroutine and produces a value. Can only be used inside a coro fn body. Supports both call and pipeline syntax.

SignatureDescription
yield(T) → VoidSuspend and produce a value.
coro fn example() Int {
    yield 1;           -- prefix form
    2 yield;           -- pipeline form
    yield(3);          -- explicit call form
}

yieldAll(coroutine)

Delegates to another coroutine, yielding all of its values. Can only be used inside a coro fn body. The inner coroutine must have the same yield type.

SignatureDescription
yieldAll(Coroutine<T>) → VoidYield all values from another coroutine.
coro fn inner() Int {
    yield 10;
    yield 20;
}

coro fn outer() Int {
    yield 1;
    yieldAll(inner());   -- yields 10, 20
    yield 99;
}
-- outer() yields: 1, 10, 20, 99

toList(coroutine)

Converts a coroutine into a lazy List<T>. The coroutine is resumed on demand as list elements are accessed, so this works with infinite coroutines when combined with lazy list operations like take.

SignatureDescription
toList(Coroutine<T>) → List<T>Convert coroutine to a lazy list.
-- Finite coroutine
count(5) toList println;            -- List(0, 1, 2, 3, 4)

-- Empty coroutine
count(0) toList println;            -- nil

-- Infinite coroutine with take
coro fn fibs() Int {
    var a = 0;
    var b = 1;
    while (true) {
        yield a;
        let tmp = a + b;
        a = b;
        b = tmp;
    }
}
fibs() toList take(8) println;      -- List(0, 1, 1, 2, 3, 5, 8, 13)

-- Chain with other list operations
count(5) toList head println;       -- 0
count(5) toList drop(3) println;    -- List(3, 4)
fibs() toList collect(6) println;   -- [0, 1, 1, 2, 3, 5]
↑ Back to top

Set Functions

Functions that operate on Set<T> values. Sets are heap-allocated hash sets passed by reference. The functions without a trailing ! (add, remove, union, …) return new sets and leave their inputs untouched. insert!, remove!, and pop! mutate in place. Sets are constructed with Set(elem1, elem2, ...).

add(set, element)

Returns a new set with the element added. The input set is unchanged.

SignatureDescription
add(Set<T>, T) → Set<T>Add an element to the set (returns a new set).

remove(set, element)

Returns a new set with the element removed. The input set is unchanged.

SignatureDescription
remove(Set<T>, T) → Set<T>Remove an element from the set (returns a new set).

remove!(set, element)

Mutating remove. Removes the element from this set in place; a no-op if it was not present. Returns the same set, allowing chained calls.

SignatureDescription
remove!(Set<T>, T) → Set<T>Remove an element in place. Returns the same set.

insert!(set, element)

Mutating insert. Adds the element to this set in place; a no-op if it was already present. Returns the same set, allowing chained calls.

SignatureDescription
insert!(Set<T>, T) → Set<T>Insert an element in place. Returns the same set.
var s = Set(1, 2, 3);
s insert!(4);
s insert!(3);                    -- duplicate; no-op
s length println;                -- 4

pop!(set)

Mutating pop. Removes and returns one arbitrary element from the set. The iteration order isn't part of the contract; this is useful for “process the set element by element” patterns. Behavior on an empty set is undefined.

SignatureDescription
pop!(Set<T>) → TRemove and return one element (mutates the set).
var s = Set(1, 2, 3);
let e = s pop!;
s length println;                -- 2
s contains(e) println;           -- false

contains(set, element)

Returns true if the element exists in the set.

SignatureDescription
contains(Set<T>, T) → BoolCheck if element exists.
let s = Set(1, 2, 3);
s contains(2) println;           -- true
s contains(5) println;           -- false

union(set1, set2)

Returns a set containing all elements from both sets.

SignatureDescription
union(Set<T>, Set<T>) → Set<T>Set union.

intersection(set1, set2)

Returns a set containing only elements present in both sets.

SignatureDescription
intersection(Set<T>, Set<T>) → Set<T>Set intersection.

difference(set1, set2)

Returns a set containing elements in the first set but not in the second.

SignatureDescription
difference(Set<T>, Set<T>) → Set<T>Set difference.
let a = Set(1, 2, 3, 4);
let b = Set(3, 4, 5, 6);
union(a, b) println;             -- Set(1, 2, 3, 4, 5, 6)
intersection(a, b) println;      -- Set(3, 4)
difference(a, b) println;        -- Set(1, 2)

toArray(set)

Converts the set to an array of its elements.

SignatureDescription
toArray(Set<T>) → [T]Convert set to array.
let s = Set(1, 2, 3);
s toArray println;               -- [1, 2, 3]

length(set) / isEmpty(set)

Returns the number of elements in the set, or whether there are none.

SignatureDescription
length(Set<T>) → IntNumber of elements in the set.
isEmpty(Set<T>) → BoolTrue when the set has no elements.

clear!(set)

Mutating clear. Removes every element in place. Returns the same set for chaining.

SignatureDescription
clear!(Set<T>) → Set<T>Remove every element (mutates the set). Returns the same set.

copy(set)

Returns a shallow copy of the set. Use when an alias needs to be mutated independently. Nested Obj* elements are shared with the original.

SignatureDescription
copy(Set<T>) → Set<T>Shallow copy of a set.
↑ Back to top

Persistent Collection Functions

Functions that operate on the two immutable persistent collection types, written with a leading #. A persistent vector has type #[T] and is written #[1, 2, 3] — an immutable indexed sequence backed by a 32-way array-mapped trie. A persistent map has type #[K: V] and is written #["a": 1, "b": 2] (empty: #[:]) — an immutable keyed collection backed by a HAMT.

These are distinct types from the mutable Array / Map (just as List is distinct from Array); there is no implicit conversion between them. Because nothing is ever mutated, the update functions reuse the plain non-mutating names — there are no ! (bang) variants. Every “update” returns a new collection that shares structure with the original, which is left untouched.

Persistent Vector

An immutable indexed sequence. Indexing is cyclic like arrays: negative and out-of-range indices wrap. In addition to the functions below, persistent vectors support structural == / !=, concatenation with $, and use as a for-loop iterable (for (x : v) { … } yields elements).

length(pvec) / isEmpty(pvec)

Returns the number of elements in the persistent vector, or whether there are none.

SignatureDescription
length(#[T]) → IntNumber of elements.
isEmpty(#[T]) → BoolTrue when there are no elements.

Subscript v[i]

Returns the element at index i. Indexing is cyclic, like arrays — negative and out-of-range indices wrap around.

let v = #[1, 2, 3];
v[0] println;                    -- 1
v[-1] println;                   -- 3
v length println;                -- 3

push(pvec, element)

Returns a new persistent vector with element appended. The input is unchanged.

SignatureDescription
push(#[T], T) → #[T]Append an element (returns a new persistent vector).
let v = #[1, 2, 3];
v push(4) println;               -- #[1, 2, 3, 4]
v println;                       -- #[1, 2, 3]   (unchanged)

put(pvec, index, element)

Returns a new persistent vector with the element at index replaced. The input is unchanged.

SignatureDescription
put(#[T], Int, T) → #[T]Replace the element at an index (returns a new persistent vector).
let v = #[1, 2, 3];
v put(1, 99) println;            -- #[1, 99, 3]

Concatenation v1 $ v2

The $ operator concatenates two persistent vectors, returning a new persistent vector.

(#[1, 2] $ #[3, 4, 5]) println;  -- #[1, 2, 3, 4, 5]

Equality == / !=

Persistent vectors compare by structure: equal if they have the same elements in the same order.

(#[1, 2, 3] == #[1, 2, 3]) println;  -- true
Collection Functions apply. Persistent vectors also support the full higher-order / sequence suite documented under Collection Functions (Arrays & Lists)map, filter, fold, scan, fold1, scan1, find, take, drop, takeWhile, dropWhile, stride, stutter, reverse, sort, sort(cmp), grade, zip, enumerate, cat, join, flatten — each returning a new persistent vector (or a scalar, for reductions). Auto-mapping also applies: a function expecting a scalar maps over a persistent vector, and @, @@, and cartesian @n all work.
let v = #[1, 2, 3];
v map(fn(x Int) { x * 10 }) println;  -- #[10, 20, 30]

Persistent Map

An immutable keyed collection. Any hashable key type works (String, Symbol, Int, …). Persistent maps support structural == / != (independent of insertion or hash order) and use as a for-loop iterable, which yields (key, value) tuples in hash order (not insertion order).

Subscript m[k]

Returns Option<V> — safe for missing keys. Use unwrap / unwrapOr to extract the value.

let m = #["x": 1, "y": 2, "z": 3];
m["x"] unwrap println;           -- 1
let im = #[1: "one", 2: "two"];
im[2] unwrap println;            -- "two"

get(pmap, key) / get(pmap, key, default)

Retrieves the value for a key. The two-argument form returns Option<V>; the three-argument form returns the value directly, using the provided default when the key is absent.

SignatureDescription
get(#[K: V], K) → Option<V>Get value for key; returns Option.some(v) or Option.none.
get(#[K: V], K, V) → VGet value for key; returns default if missing.
let m = #["x": 1, "y": 2, "z": 3];
m get("z") unwrap println;       -- 3
m get("w", 0) println;           -- 0  (absent, returns default)

contains(pmap, key)

Returns true if the key exists in the persistent map.

SignatureDescription
contains(#[K: V], K) → BoolCheck if key exists.
let m = #["x": 1, "y": 2];
m contains("x") println;         -- true

length(pmap) / isEmpty(pmap)

Returns the number of key-value pairs in the persistent map, or whether there are none.

SignatureDescription
length(#[K: V]) → IntNumber of entries.
isEmpty(#[K: V]) → BoolTrue when there are no entries.

put(pmap, key, value)

Returns a new persistent map with the key-value pair inserted or updated. The input is unchanged.

SignatureDescription
put(#[K: V], K, V) → #[K: V]Insert-or-update a key-value pair (returns a new persistent map).
let m = #["x": 1, "y": 2, "z": 3];
m put("w", 4) length println;    -- 4
m length println;                -- 3   (m unchanged)

remove(pmap, key)

Returns a new persistent map without the given key. The input is unchanged.

SignatureDescription
remove(#[K: V], K) → #[K: V]Remove a key (returns a new persistent map).
let m = #["x": 1, "y": 2];
m remove("x") contains("x") println;  -- false

keys(pmap) / values(pmap)

Return the keys (resp. values) of the persistent map as a persistent vector.

SignatureDescription
keys(#[K: V]) → #[K]All keys, as a persistent vector.
values(#[K: V]) → #[V]All values, as a persistent vector.

merge(pmap1, pmap2)

Returns a new persistent map containing all entries from both. On a key conflict, the right operand (pmap2) wins. Both inputs are unchanged.

SignatureDescription
merge(#[K: V], #[K: V]) → #[K: V]Merge two persistent maps (second wins on conflicts).
(#["a": 1, "b": 2] merge(#["b": 20, "c": 30]))["b"] unwrap println;  -- 20

Equality == / !=

Persistent maps compare by structure — equal if they contain the same key-value pairs, independent of insertion or hash order.

(#["a": 1, "b": 2] == #["b": 2, "a": 1]) println;  -- true

Conversions

Four functions convert between the mutable and persistent collection types. These are real O(n) builds, not cheap freeze/thaw views — the converted result is fully independent of any later mutation of the source.

SignatureDescription
toPersistentVector([T]) → #[T]Build a persistent vector from an array.
toArray(#[T]) → [T]Build a mutable array from a persistent vector. (toArray also accepts arrays, sets, etc.)
toPersistentMap([K: V]) → #[K: V]Build a persistent map from a mutable map.
toMap(#[K: V]) → [K: V]Build a mutable map from a persistent map.
[5, 6, 7] toPersistentVector toArray println;   -- [5, 6, 7]
["x": 1, "y": 2] toPersistentMap toMap length println;  -- 2

-- The persistent copy is independent of later mutation:
var src = [1, 2, 3];
let f = src toPersistentVector;
src push!(4);
src length println;              -- 4
f length println;                -- 3   (f unaffected)
↑ Back to top

Hashing & Equality

hash(x)

Returns an integer hash value for any value. Consistent across calls: equal values always produce the same hash. User-defined hash overloads take priority over the built-in implementation.

SignatureDescription
hash(Int) → IntHash an integer.
hash(Float) → IntHash a float.
hash(Bool) → IntHash a boolean.
hash(Symbol) → IntHash a symbol.
hash(T) → IntHash any object type (String, Array, Tuple, Struct, Enum, etc.). Uses structural hashing.
hash(42) println;                -- integer hash value
hash(3.14) println;              -- integer hash value
hash('foo) println;              -- integer hash value

-- Equal values produce equal hashes
println(hash((1, 2)) == hash((1, 2)));     -- true
println(hash([1, 2, 3]) == hash([1, 2, 3]));  -- true

Universal Equality (== / !=)

The == and != operators work on all types with matching operands. For object types (Structs, Tuples, Arrays, Enums, etc.), structural equality is used: two values are equal if all their fields/elements are equal. User-defined == overloads take priority over built-in structural equality.

-- Struct equality
struct Point { x Int, y Int }
let p1 = Point{1, 2};
println(p1 == Point{1, 2});      -- true
println(p1 == Point{3, 4});      -- false

-- Tuple equality
println((1, 2) == (1, 2));       -- true

-- Array equality
println([1, 2, 3] == [1, 2, 3]); -- true

-- Symbol equality
println('foo == 'foo);           -- true

-- Enum equality
enum Color { red, green, blue }
println(Color.red == Color.red); -- true

-- User-defined == overrides structural equality
struct Vec2 { x Float, y Float }
fn ==(a Vec2, b Vec2) Bool {
    let dx = a.x - b.x;
    let dy = a.y - b.y;
    dx * dx + dy * dy < 0.001    -- approximate equality
}

Cyclic Values

Both == and hash are cycle-safe: values that reference themselves (built through Ref assignment or in-place Array/Map/Set mutation) compare and hash without recursing forever. The common acyclic case pays essentially nothing — the ordinary recursive fast path runs first with a small work budget, and only a huge value or an actual cycle falls back to the cycle-aware algorithm.

Equality of cyclic values is bisimulation: two graphs are equal if their infinite unrollings are equal. Two separately built but identically shaped cycles compare equal, and shared substructure is detected so comparing large shared graphs stays fast. Isomorphic separately-built cycles also hash identically, so cyclic values work as Map keys and Set elements.

enum Tree { node [Tree], leaf Int }

var a = [Tree.leaf(1)];
a push!(Tree.node(a));           -- a[1] refers back to a: a cycle
var b = [Tree.leaf(1)];
b push!(Tree.node(b));           -- an independently built equal cycle

println(a == b);                 -- true  (bisimulation)
println(hash(a) == hash(b));     -- true
var m = [Tree.node(a): "found"]; -- cyclic values as map keys work
m[Tree.node(b)] unwrap println;  -- found

One edge case: bisimilar cycles of different unrolled length (a 1-node cycle versus its hand-built 2-node unrolling) compare equal but may hash differently. Such values are pathological as hash keys; everything structurally built stays consistent.

== and hash on Bytes compare and hash the byte contents (structural, like String).

↑ Back to top

Serialization

Data values — including cyclic object graphs — can be serialized to a compact binary form (Bytes) and restored. "Data" means scalars and containers of them; types that carry behavior or live runtime state (functions, coroutines, futures, actors, Any, existentials) are not serializable — see the exact list under serialize below. Shared substructure is written once and cycles are stored as references, so serialization never duplicates or diverges. The bytes are deterministic: equal values produce identical bytes regardless of how they were built (Map/Set entries are written in a canonical order), which makes hash(serialize(x)) a stable content address.

serialize(x)

SignatureDescription
serialize(T) → BytesEncode a value of any serializable type into a self-describing binary buffer.

Serializable: Bool, Int, Float, Symbol, String, Bytes, Complex, Fraction, ranges, arrays, lists, maps, sets, refs, tuples, structs, enums (including recursive ones), and persistent collections — any composition of these. Types containing functions, coroutines, futures, actors, Any, or existentials are rejected at compile time.

deserialize<T>(b)

SignatureDescription
deserialize<T>(Bytes) → TDecode a buffer produced by serialize. The target type is given as an explicit type argument.

The buffer embeds a structural signature of the type it was serialized as (field and case names, recursive layout). deserialize<T> validates it against T exactly; a mismatch — or any malformed/truncated input — raises a clean runtime error rather than decoding garbage. Compatibility is structural: an identical declaration in another program (same shape and the same spelled names) round-trips; renaming a field or case intentionally breaks it.

-- Round trip any data value
struct Point { x Float, y Float }
let p = Point { x: 1.5, y: 2.5 };
let b = serialize(p);                        -- Bytes
println(deserialize<Point>(b) == p);         -- true

-- Cycles and sharing survive: aliases stay aliases
let shared = &10;
let out = deserialize<[Ref<Int>]>(serialize([shared, shared]));
out[0] <- 99;
*(out[1]) println;                           -- 99  (still one Ref)

-- Deterministic bytes: construction order does not matter
var m1 = ["a": 1]; m1["b"] = 2;
var m2 = ["b": 2]; m2["a"] = 1;
println(serialize(m1) == serialize(m2));     -- true

-- Wrong type or corrupt bytes: runtime error, never garbage
-- deserialize<Float>(serialize(42));       -- error: type signature mismatch

Bounded decoding. Declared sizes in a buffer are validated against its length, so a corrupt or hostile buffer cannot cause runaway allocation. Lazy lists are forced during serialization (bounded; an unbounded list raises a runtime error).

↑ Back to top

Enum Functions

ordinal(enum_value)

Returns the zero-based index of the enum case.

SignatureDescription
ordinal(Enum) → IntGet the case index of an enum value.
enum Color { red, green, blue }
ordinal(Color.red) println;      -- 0
ordinal(Color.green) println;    -- 1
ordinal(Color.blue) println;     -- 2

tag(enum_value)

Returns the case name of an enum value as a Symbol.

SignatureDescription
tag(Enum) → SymbolGet the case name of an enum value.
enum Color { red, green, blue }
tag(Color.red) println;      -- 'red
tag(Color.green) println;    -- 'green
tag(Color.blue) println;     -- 'blue

enum Option { some Int, none }
tag(Option.some(42)) println;  -- 'some
tag(Option.none) println;      -- 'none
↑ Back to top

Built-in Operators

The following operators are compiled directly to VM instructions. They are not callable as named functions but are fundamental to the language. Arithmetic operators also work element-wise on Arrays and Tuples of numeric types.

Arithmetic Operators

OperatorTypesDescription
+Int, Float, Fraction, ComplexAddition.
-Int, Float, Fraction, ComplexSubtraction.
*Int, Float, Fraction, ComplexMultiplication.
/Int, Float, Fraction, ComplexDivision. Int division produces a Fraction.
%IntModulo (remainder).
- (unary)Int, Float, Fraction, ComplexNegation.
//IntTruncating integer division.

Comparison Operators

OperatorTypesDescription
==All types (matching operands)Equal. Structural equality for object types. User-defined overloads take priority.
!=All types (matching operands)Not equal. Structural inequality for object types. User-defined overloads take priority.
<Int, Float, Fraction, StringLess than.
<=Int, Float, Fraction, StringLess than or equal.
>Int, Float, Fraction, StringGreater than.
>=Int, Float, Fraction, StringGreater than or equal.

Bitwise Operators

OperatorTypesDescription
&IntBitwise AND.
|IntBitwise OR.
^IntBitwise XOR.
~IntBitwise NOT (complement).
<<IntLeft shift.
>>IntRight shift (arithmetic, sign-extending).
>>>IntUnsigned right shift (zero-filling).

Logical Operators

OperatorTypesDescription
!BoolLogical NOT.
&&BoolLogical AND (short-circuit).
||BoolLogical OR (short-circuit).

Concatenation Operator

OperatorTypesDescription
$String, Array, List, TupleConcatenation.
↑ Back to top

Type Conversions

Implicit conversions follow the numeric tower: Int → Fraction → Float → Complex. Implicit conversions are inserted automatically when a value of a lower-ranked type is used where a higher-ranked type is expected.

Explicit conversion functions are provided for all numeric types, including conversions down the tower (e.g. Float to Int):

toInt(x)

Convert a numeric value to Int by truncating toward zero.

SignatureDescription
toInt(Int) → IntIdentity — returns the value unchanged.
toInt(Float) → IntTruncate toward zero (fractional part discarded).
toInt(Fraction) → IntTruncate toward zero (numerator/denominator as float, then truncated).
toInt(3.7) println;            -- 3
toInt(-2.9) println;           -- -2
toInt(7/2) println;             -- 3

toFloat(x)

Convert a numeric value to Float.

SignatureDescription
toFloat(Int) → FloatInteger to double-precision float.
toFloat(Float) → FloatIdentity — returns the value unchanged.
toFloat(Fraction) → FloatFraction to double-precision float (numerator/denominator).
toFloat(Complex) → FloatReturns the real part, discarding the imaginary part.
toFloat(5) println;             -- 5.0
toFloat(7/2) println;           -- 3.5
toFloat(3.0 + 4.0i) println;   -- 3.0

toFraction(x)

Convert a numeric value to Fraction.

SignatureDescription
toFraction(Int) → FractionInteger to fraction (denominator 1).
toFraction(Fraction) → FractionIdentity — returns the value unchanged.
toFraction(5) println;         -- 5/1

toComplex(x)

Convert a numeric value to Complex (imaginary part 0).

SignatureDescription
toComplex(Int) → ComplexInteger to complex (imaginary part 0).
toComplex(Float) → ComplexFloat to complex (imaginary part 0).
toComplex(Fraction) → ComplexFraction to complex (via float, imaginary part 0).
toComplex(Complex) → ComplexIdentity — returns the value unchanged.
toComplex(5) println;          -- 5+0i
toComplex(3.14) println;       -- 3.14+0i
toComplex(7/2) println;        -- 3.5+0i
↑ Back to top

I/O Functions

FunctionDescription
print(...)Print values without a trailing newline. Accepts any number of arguments of any type. Does not flush output.
println(...)Print values followed by a newline. Accepts any number of arguments of any type. Flushes output.

Both print and println accept any number of arguments of any type. Multiple arguments are separated by spaces. They participate in auto-mapping: passing an array with @ prints each element individually.

-- println prints values followed by a newline
println(42);                       -- 42
println("hello", "world");        -- hello world
println(1, 2.5, true);            -- 1 2.5 true
println();                          -- (empty line)

-- print does not add a newline
print("x=");
println(42);                       -- x=42

-- Pipeline syntax
42 println;                         -- 42
"hello" println;                    -- hello

-- Auto-mapping: print each element on its own line
[1, 2, 3] @ println;                -- 1  2  3  (each on its own line)

panic(message)

Print Error: <message> and halt execution — the same trap unwrap springs on a none, with a message of your choosing. Statements after the panic do not run; a script that panics exits with a nonzero status. Use it in library code that must not continue past a failure — for example, defSynth/defSynthX panic when a synthdef fails to compile.

SignatureDescription
panic(String) → VoidPrint the message and halt execution.
"before" println;                  -- before
panic("something went wrong");     -- Error: something went wrong
"after" println;                    -- never runs

toString(x)

Convert any value to its printed representation. The result is exactly the text that print would output for the value, as a single-line String with no width awareness (see prettyString below for the width-aware form). Works on values of every type: numbers, Bool, Symbol, String, containers, tuples, structs, enums, and so on.

SignatureDescription
toString(T) → StringThe printed form of the value. For a String argument this is the identity.

Note that strings are rendered without quotes, both at top level and inside containers, and symbols are rendered without the leading '.

toString(42) println;                    -- 42
toString(7/2) println;                   -- 7/2
toString(3.0 + 4.0i) println;           -- 3+4i
toString([1, 2, 3]) println;            -- [1, 2, 3]
toString(["a", "b"]) println;          -- [a, b]  (strings unquoted)
toString((1, "two", 3.0)) println;     -- (1, two, 3.0)

struct Point { x Float; y Float; }
toString(Point { x: 1.5, y: 2.5 }) println;
                                        -- Point { x: 1.5, y: 2.5 }

-- Useful for building strings with the $ concatenation operator
let s = "answer: " $ toString(42);
s println;                              -- answer: 42

-- Pipeline syntax
[1, 2, 3] toString println;             -- [1, 2, 3]

prettyString(x) / prettyPrint(x)

Width-aware pretty printing. Each container prints on one line when it fits the target width, and otherwise breaks one element per line with two-space indentation. At a very large width the output is identical to toString. The REPL and notebook cells display results this way automatically.

SignatureDescription
prettyString(T) → StringRender at the default width (80 columns).
prettyString(T, Int) → StringRender at the given line width.
prettyPrint(T) → Void
prettyPrint(T, Int) → Void
Print the pretty form followed by a newline.
let v = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
v prettyPrint;                 -- fits in 80 columns: one line
-- [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

v prettyPrint(20);             -- narrower: one element per line
-- [
--   [1, 2, 3],
--   [4, 5, 6],
--   [7, 8, 9]
-- ]

struct Point { x Float, y Float }
Point { x: 1.5, y: 2.5 } prettyPrint(12);
-- Point {
--   x: 1.5,
--   y: 2.5
-- }

Cyclic values print safely — in both print/toString and the pretty printer. A container that re-enters itself prints ^n^, meaning “this is the same object n container levels up,” and recursion stops there:

enum Tree { node [Tree], leaf Int }
var a = [Tree.leaf(1)];
a push!(Tree.node(a));
a println;                     -- [Tree.leaf(1), Tree.node(^1^)]

List Print Limit

Control how many elements are displayed when printing lists.

FunctionSignatureDescription
getListPrintLimit()() → IntGet the current maximum number of list elements to display when printing.
setListPrintLimit(n)Int → IntSet the maximum number of list elements to display. Returns the previous limit.
let old = setListPrintLimit(3);
(1..10) toList println;        -- List(1, 2, 3, ...)
setListPrintLimit(old);        -- restore previous limit
getListPrintLimit();           -- check current limit

Graph-Traversal Limits

Per-VM safety limits on the cycle-safe == / hash / print / serialize traversals. Each limit guards against a runaway traversal (a C++ stack overflow, or forcing an unbounded lazy list forever); a valid program that legitimately exceeds one can raise it at runtime. Setters return the previous value and clamp to >= 1. Initial values come from the host's VM configuration (in the app: Language Settings).

FunctionSignatureDescription
getGraphMaxDepth()() → IntCurrent nesting-depth limit for ==, hash, and serialize traversals (default 10000). Exceeding it raises a runtime error.
setGraphMaxDepth(n)Int → IntSet the traversal depth limit. Returns the previous limit.
getLazyForceLimit()() → IntCurrent cap on lazy list-node forces per ==/hash/serialize traversal (default 10000), so an unbounded lazy list raises instead of hanging.
setLazyForceLimit(n)Int → IntSet the lazy force cap. Returns the previous limit.
getPrintMaxDepth()() → IntCurrent container-nesting depth at which printing elides with ... (default 200).
setPrintMaxDepth(n)Int → IntSet the print-depth limit. Returns the previous limit.
let old = setGraphMaxDepth(50000);
deepA == deepB;                -- a 20000-deep compare now succeeds
setGraphMaxDepth(old);         -- restore previous limit
↑ Back to top

File & OS Functions NRT

Whole-file reads and writes, directory operations, and environment access. Every function in this section performs blocking syscalls and is not real-time safe: calling one in a program compiled for a real-time VM (--rt, silo code) is a compile-time type error. Fallible operations return Option (none on any OS error); mutating operations return a success Bool.

There are no streaming file handles — files are read and written whole. For structured binary data, use readFileBytes with the Bytes accessors (u8At, f64At, utf8At, …). Ergonomic wrappers (readLines, readFileOr, Result-returning variants) live in the std.fs module; pure path-string helpers live in std.path (see the Standard Library reference).

Reading and Writing

SignatureDescription
readFile(String) → Option[String]The file's entire contents as a string.
readFileBytes(String) → Option[Bytes]The file's entire contents as bytes.
writeFile(String, String) → Bool
writeFile(String, Bytes) → Bool
Replace the file's contents; true on success.
appendFile(String, String) → Bool
appendFile(String, Bytes) → Bool
Append to the file (creating it if missing).
writeFile("/tmp/notes.txt", "hello\n");
appendFile("/tmp/notes.txt", "world\n");
readFile("/tmp/notes.txt") unwrapOr("") print;   -- hello\nworld\n
readFile("/missing") isNone;                      -- true

Async Variants

Each read/write builtin has an Async variant returning a Future. The builtin returns immediately; the syscall runs on the host's I/O worker thread and the Future resolves when it lands, so a long read no longer stalls the VM (actor service, OSC/NATS handlers, UI callbacks keep running). await the Future for the same value the synchronous form returns. Completion order is unobservable — awaited values arrive in program order — and in a host with no I/O worker (an embedded bare VM) the variants degrade to synchronous execution with identical semantics. They are still NRT: a Pending Future is cheap, but resolving one with file contents on the audio thread is not, so real-time VMs reject them at compile time like the rest of this section.

SignatureDescription
readFileAsync(String) → Future[Option[String]]Off-thread readFile.
readFileBytesAsync(String) → Future[Option[Bytes]]Off-thread readFileBytes.
writeFileAsync(String, String) → Future[Bool]
writeFileAsync(String, Bytes) → Future[Bool]
Off-thread writeFile.
appendFileAsync(String, String) → Future[Bool]
appendFileAsync(String, Bytes) → Future[Bool]
Off-thread appendFile.
-- Start both reads, then await: the files load concurrently.
let a = readFileAsync("/tmp/one.txt");
let b = readFileAsync("/tmp/two.txt");
(await a) unwrapOr("") println;
(await b) unwrapOr("") println;

Metadata and Directories

SignatureDescription
fileExists(String) → BoolTrue for any existing entry (file or directory).
isDirectory(String) → BoolTrue if the path is a directory.
fileSize(String) → Option[Int]Size in bytes of a regular file.
fileModTime(String) → Option[Float]Last-modified time, Unix seconds.
listDir(String) → Option[[String]]Entry names, sorted; no "."/"..".
makeDir(String) → BoolCreate the directory and missing parents; true if it exists afterwards.
removeFile(String) → BoolDelete a file (not a directory).
renameFile(String, String) → BoolRename/move a file.
makeDir("/tmp/proj/samples");
listDir("/tmp/proj") unwrap println;   -- [samples]
fileSize("/tmp/kick.wav") unwrapOr(-1);

Environment and Process

SignatureDescription
getEnv(String) → Option[String]An environment variable's value.
programArgs() → [String]CLI arguments after the script filename (tzpl run.x a b[a, b]).
currentDir() → Option[String]The process's current working directory.
getEnv("HOME") unwrapOr("/") println;
programArgs() println;
↑ Back to top

FFT Functions NRT

Real FFT over [Float] in double precision. The input length must be a power of two ≥ 4; anything else returns none. Spectra use a packed split-complex layout of the same length N as the signal: [re[0], re[1], …, re[N/2-1], im[0], im[1], …, im[N/2-1]], where re[0] is the DC component and im[0] carries the (purely real) Nyquist component. fft produces the standard unnormalized DFT (a full-scale cosine at bin k yields re[k] = N/2); ifft is its exact inverse: ifft(fft(x)) unwrap == x to double precision.

Not real-time safe (FFT setups are cached with the system allocator): calling either in a program compiled for a real-time VM is a compile-time type error. Typical uses — wavetable generation (see wavetables.x and the oscillators chapter of Writing SynthDefs), offline analysis — run at patch-build time.

SignatureDescription
fft([Float]) → Option[[Float]]Forward real FFT: N time samples → packed split spectrum. none unless N is a power of two ≥ 4.
ifft([Float]) → Option[[Float]]Inverse real FFT: packed split spectrum → N time samples. Exact inverse of fft.
-- Build one period of a unit-amplitude sine directly in the spectrum:
-- partial k at amplitude a is im[k] = -a*N/2 (sine) / re[k] = a*N/2 (cosine).
let n = 512;
var spec = [Float]();
for (i : (1 .. n)) { spec push!(0.0); }
spec[n // 2 + 1] = -(n toFloat) / 2.0;   -- im[1]: sin(2*pi*x)
let table = ifft(spec) unwrap;

fft([1.0, 2.0, 3.0]) isNone;             -- true: not a power of two

Introspection

disassemble(fn)

Print the generated bytecode for a function. Accepts any function value — named functions, lambdas, and closures. For built-in (primitive) functions, which have no bytecode, a message is printed instead.

SignatureDescription
fn(...) T → VoidPrint the instruction stream for the given function.

The output includes a header with the function name, register count, argument count, and type signature, followed by each instruction with its offset, opcode name, register operands, and any constants, jump targets, or type annotations.

-- Disassemble a named function
fn add(a Int, b Int) Int = a + b;
disassemble(add);
-- Output:
-- -- add  (3 regs, 2 args)
--      0  ADD_INT                 r2, r0, r1
--      2  RETURN                  r2

-- Disassemble a lambda
let double = fn(x Int) Int = x * 2;
disassemble(double);
-- Output:
-- -- <lambda>  (3 regs, 1 args, type fn(Int) Int)
--      0  LOAD_INT                r1  ;  2
--      3  MUL_INT                 r2, r0, r1
--      5  RETURN                  r2

-- Disassemble a recursive function
fn fib(n Int) Int {
  if (n <= 1) { return n; }
  else { return fib(n - 1) + fib(n - 2); }
}
disassemble(fib);
-- Shows CALL instructions with resolved function names like (fib)

typeName<T>()

Returns the display name of a type, given as an explicit type argument. (Built-in functions can take explicit type arguments with the f<T>(…) call form — the same syntax deserialize<T> uses.)

SignatureDescription
typeName<T>() → StringThe display name of T.
typeName<Int>() println;             -- Int
typeName<[Int]>() println;           -- [Int]
typeName<[String: Float]>() println;  -- [String:Float]
typeName<(Int, Bool)>() println;      -- (Int, Bool)

typeRepr(x)

A low-level debugging aid that prints how a value's type is represented internally: its representation class (Atom, Inline, Pointer, …), its size in 64-bit words, whether it is a value type, whether it is recursive, and — for composites — the field layout with word offsets. The argument is a normal value; its static type is what gets described. Intended for understanding memory layout and inline-promotion decisions, not for use in programs.

SignatureDescription
typeRepr(T) → VoidPrint the internal representation of T to standard output.
typeRepr(42);
-- Int: repr=Atom sizeWords=1 value=1 recursive=0

typeRepr(7/2);
-- Fraction: repr=Inline sizeWords=2 value=1 recursive=0

struct Point { x Float; y Float; }
typeRepr(Point { x: 1.5, y: 2.5 });
-- Point: repr=Inline sizeWords=2 value=1 recursive=0 inline=2 layout=[(@0,1w,Float),(@1,1w,Float)]

typeRepr([1, 2, 3]);
-- [Int]: repr=Pointer sizeWords=1 value=1 recursive=0

Note: disassemble and typeRepr are not real-time safe — they write to standard output. For disassemble, if a function has multiple overloads you must disambiguate by binding it to a variable with a specific type first, since overloaded names cannot be used as values directly.

↑ Back to top

Summary by Type

Quick reference of which named functions are available for each type.

Int

abs, sign, min, max, clamp, cmp, gcd, lcm, clz, clo, ctz, cto, popCount, rotl, rotr, bitCeil, bitFloor, bitWidth, hasSingleBit, hash

Float

abs, sign, min, max, clamp, cmp, floor, ceil, round, trunc, frac, sqrt, cbrt, pow, hypot, exp, exp2, exp10, expm1, log, log2, log10, log1p, sin, cos, tan, asin, acos, atan, atan2, sinpi, cospi, tanpi, sinh, cosh, tanh, asinh, acosh, atanh, erf, erfc, tgamma, lgamma, copysign, nextafter, isNan, isInf, isFinite, isNormal, hash

Fraction

abs, sign, min, max, clamp, cmp, numer, denom, hash

Complex

abs, sqrt, pow, real, imag, arg, norm, conj, polar, exp, log, sin, cos, tan, asin, acos, atan, sinh, cosh, tanh, asinh, acosh, atanh, hash

String

length, substring, contains, startsWith, endsWith, split, trim, toUpper, toLower, replace, codePoints, fmt, s[i] (byte indexing), toSymbol, min, max, cmp, hash

Symbol

hash

Range<Int>

length, toArray, toList

[T]

length, map, filter, fold, scan, fold1, scan1, find, zip, enumerate, take, drop, takeWhile, dropWhile, stride, stutter, cat, join, flatten, reverse, push, pop, sort, muss, hash

List<T>

length, head, tail, cons, isNil, notNil, map, filter, fold, scan, fold1, scan1, find, zip, enumerate, take, drop, takeWhile, dropWhile, stride, stutter, cat, join, flatten, cyc, ncyc, hang, iter, hash

Coroutine<T>

next, toList

[K: V]

length, get, put, remove, contains, keys, values, merge, hash

Set<T>

length, add, remove, contains, union, intersection, difference, toArray, hash

Enum

ordinal, tag, hash

Ref<T>

ref, deref, setref

Any

any, toAnyArray, as(Type)

Function Types

disassemble

All Types

==, != (structural equality), hash