Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Records & ADTs

Records are milang’s primary data structure. They hold named fields, support structural updates, and form the basis of algebraic data types (ADTs).

Record Literals

A record is a set of field = value pairs inside braces, separated by ; or newlines:

point = {x = 3; y = 4}
person = {name = "Alice"; age = 30}
build =  {target = c, os = linux, arch = x86_64}
point =  {x = 3, y = 4}
person =  {name = Alice, age = 30}

Field Access

Use dot notation to read a field. Dots chain for nested records.

point = {x = 3; y = 4}
a = point.x
b = point.y
build =  {target = c, os = linux, arch = x86_64}
point =  {x = 3, y = 4}
a = 3
b = 4

Positional Access

Fields can also be accessed by declaration order using _0, _1, etc.:

pair = {first = "hello"; second = "world"}
a = pair._0
b = pair._1
build =  {target = c, os = linux, arch = x86_64}
pair =  {first = hello, second = world}
a = hello
b = world

Record Update

The <- operator creates a new record with selected fields replaced. Fields not mentioned are carried over unchanged.

base = {x = 1; y = 2; z = 3}
moved = base <- {x = 10; z = 30}
build =  {target = c, os = linux, arch = x86_64}
base =  {x = 1, y = 2, z = 3}
moved =  {x = 10, y = 2, z = 30}

Destructuring

Bind fields from a record directly into the current scope. Use {field} for same-name bindings, or {local = field} to rename:

point = {x = 3; y = 4}
{x; y} = point
sum = x + y
__p_sum = <closure>
build =  {target = c, os = linux, arch = x86_64}
point =  {x = 3, y = 4}
_destruct_23 =  {x = 3, y = 4}
x = 3
y = 4

Renaming during destructuring:

point = {x = 3; y = 4}
{myX = x; myY = y} = point
result = myX + myY
build =  {target = c, os = linux, arch = x86_64}
point =  {x = 3, y = 4}
_destruct_23 =  {x = 3, y = 4}
myX = 3
myY = 4
result = 7

Scope-as-Record

When a function body has no explicit result expression — just indented bindings — the named bindings are collected into an implicit record:

makeVec x y =
  magnitude = x + y
  product = x * y
v = makeVec 3 4
build =  {target = c, os = linux, arch = x86_64}
makeVec = <closure>
v =  {magnitude = 7, product = 12}

Bare expressions (not bound to a name) execute for their side effects and are not included in the returned record. This is how main works — see the Scopes chapter.

ADTs (Algebraic Data Types)

An uppercase name bound to braces containing uppercase constructors declares a tagged union:

Shape = {Circle radius; Rect width height; Point}
c = Circle 5
r = Rect 3 4
p = Point
build =  {target = c, os = linux, arch = x86_64}
Shape = _module_ {Circle = <closure>, Rect = <closure>, Point = Point {}}
Circle = <closure>
Rect = <closure>
Point = Point {}
c = Circle {radius = 5}
r = Rect {width = 3, height = 4}
p = Point {}

Each constructor becomes a function that produces a tagged record. Zero-field constructors (like Point above) are plain tagged records with no arguments.

Constructors are also available namespaced under the type name (e.g. Shape.Circle).

Constructors as Functions

Because constructors are just functions, they work naturally with map and other higher-order functions:

values = map (\x -> Just x) [1, 2, 3]
__p_values = <closure>
build =  {target = c, os = linux, arch = x86_64}

Pattern Matching on Tags

Use the -> operator to match on a value’s constructor tag. After a tag matches, the record’s fields are accessible via dot notation or destructuring:

Shape = {Circle radius; Rect width height}
area shape = shape ->
  Circle = 3.14 * shape.radius * shape.radius
  Rect = shape.width * shape.height
a = area (Circle 5)
b = area (Rect 3 4)
build =  {target = c, os = linux, arch = x86_64}
Shape = _module_ {Circle = <closure>, Rect = <closure>}
Circle = <closure>
Rect = <closure>
area = <closure>
a = 78.5
b = 12

Named-field destructuring in alternatives:

Shape = {Circle radius; Rect width height}
area shape ->
  Circle {radius} = 3.14 * radius * radius
  Rect {width; height} = width * height
a = area (Circle 5)
b = area (Rect 3 4)
build =  {target = c, os = linux, arch = x86_64}
Shape = _module_ {Circle = <closure>, Rect = <closure>}
Circle = <closure>
Rect = <closure>
area = <closure>
a = 78.5
b = 12

See the Pattern Matching chapter for the full range of patterns, guards, and list matching.

Record Introspection

Several built-in functions let you inspect records dynamically:

FunctionReturns
fields rList of {name, value} records ([] for non-records)
fieldNames rList of field-name strings
tag rConstructor tag string, or "" for untagged values
getField r "name"Just value if present, Nothing if missing
setField r "name" valNew record with field set