3. Types and the Numeric Tower
3.1 Built-in Types
Int -- 64-bit signed integer
Float -- 64-bit double precision float
Bool -- boolean (stored as 64-bit integer)
String -- UTF-8 string
Symbol -- interned string, compared by pointer
Fraction -- rational number (64-bit numerator/denominator)
Complex -- complex number (two 64-bit floats)
Void -- no value
3.2 Numeric Tower
Types promote automatically: Int → Fraction → Float → Complex
-- Fraction literals (no spaces around /)
let half = 1/2; -- 1/2 (fraction)
let third = 1/3; -- 1/3 (fraction)
let sum = half + third; -- 5/6 (fraction)
-- Int division also produces a fraction
let q = 7 / 4; -- 7/4 (fraction)
-- Int + float promotes to float
let x = 2 + 3.5; -- 5.5 (float)
-- Imaginary literals create complex numbers
let z = 3.0 + 4i; -- (3+4i) (complex)
let w = Complex(1.0, -1.0); -- (1-1i)
3.3 Collection Types
-- Array type (homogeneous)
[Int] -- array of integers
[Float] -- array of floats
[String] -- array of strings
-- Array literals
let nums = [1, 2, 3, 4, 5];
let floats = [1.0, 2.0, 3.0];
-- Tuple literals (heterogeneous, fixed-length)
let point = (3, 4);
let mixed = (1, 2.5, "hello");
-- 1-element tuple (trailing comma distinguishes from parenthesized expression)
let single = (42,); -- type: (Int,)
single println; -- (42,)
single.0 println; -- 42
-- Empty tuple (unit value)
let u = (); -- type: ()
u println; -- ()
-- Trailing commas are allowed in all tuples
let t3 = (1, 2, 3,); -- same as (1, 2, 3)
-- Map literals (key: value pairs)
let ages = ["Alice": 30, "Bob": 25];
-- Set construction
let unique = Set(1, 2, 3);
-- Persistent (immutable) collections: a leading # on the type and literal
let pv #[Int] = #[1, 2, 3]; -- persistent vector
let pm #[String:Int] = #["a": 1]; -- persistent map
-- Access by index
let first = nums[0]; -- array index
let x = point.0; -- tuple field access
-- Array indexing is cyclic (wraps around)
let a = [10, 20, 30];
a[0] -- 10
a[3] -- 10 (wraps around)
a[-1] -- 30 (last element)
a[-2] -- 20
-- Indexed assignment: writes also wrap cyclically
var b = [10, 20, 30];
b[0] = 100;
b[-1] = 999;
b println; -- [100, 20, 999]
-- Mutating builtins: push! / pop! / clear! modify the array in place
b push!(42);
b println; -- [100, 20, 999, 42]
let last = b pop!;
last println; -- 42
b clear!;
b isEmpty println; -- true (isEmpty works on every collection)
-- Non-mutating push / pop return a new array (input unchanged)
let orig = [1, 2, 3];
let extended = orig push(99);
orig println; -- [1, 2, 3]
extended println; -- [1, 2, 3, 99]
-- Arrays are passed by reference; copy when an alias must be independent
var c = [1, 2, 3];
var d = c copy;
c push!(4);
c println; -- [1, 2, 3, 4]
d println; -- [1, 2, 3]
Mutating-function names: the trailing !
Names ending in ! (push!, pop!, insert!, …) mutate their first argument in place; names without ! return new values and leave their inputs untouched. The ! is part of the identifier — foo and foo! are different functions. The convention is idiomatic but not enforced; a user-defined mutating function can be named with or without ! as you choose.
Mutable vs. persistent collections
Array, Map, and Set are mutable heap objects passed by reference. The #[…] forms are persistent (immutable) collections — a persistent vector and a persistent map — that never change in place: every update returns a new value sharing structure with the old one. They are distinct types with no implicit conversion to or from the mutable kinds. See Persistent Collections.