Skip to content

Types

Gale builds richer types from fixed-length tuples, aliases, literal unions, and recursive algebraic data types. Generic parameters use angle brackets. Exhaustive case covers every finite shape.

mod types
pub type Pair<A, B> = {A, B}
pub fn make_pair(left: Integer, right: String) -> Pair<Integer, String> {
{left, right}
}

An atom literal such as :ok is also a type containing one value. Joining literal types with | creates a finite union that case can cover exhaustively. Boolean is the built-in union :true | :false. Nilable<T> is shorthand for T | nil; it uses the same union checking and pattern narrowing rules. It is especially useful for nullable native fields.

mod types
pub fn label(status: :ok | :error) -> String {
case status {
:ok -> "ok"
:error -> "error"
}
}

Guard type tests narrow a broader value inside their arm. Gale emits the native Elixir guard. It does not synthesize guards merely because a function parameter already has a precise type.

mod types
pub fn as_binary(value: Term) -> Option<Binary> {
case value {
binary when is_binary(binary) -> Some(binary)
_ -> None
}
}

An algebraic data type names its constructors and their payloads. ADTs can be recursive: Tree<A> contains either Leaf or a node holding a value and two more trees. Nullary constructors emit snake-case atoms; constructors with payloads emit tagged tuples.

mod types
pub type Tree<A> =
| Leaf
| Node(A, Tree<A>, Tree<A>)
pub fn sum(tree: Tree<Integer>) -> Integer {
case tree {
Leaf -> 0
Node(n, left, right) -> n + sum(left) + sum(right)
}
}