Standard Library

The standard library is self-hosted – written in Cure under lib/std/. Compile it with mix cure.compile_stdlib or cure stdlib. The output goes to _build/cure/ebin/ as regular .beam files callable from Erlang or Elixir.

18 modules. ~200 functions. No Elixir runtime dependency beyond FFI calls to :erlang, :math, :maps, :lists, :string, :binary, :io, and :os.

All modules are documented with ## doc comments. Run cure doc lib/std/ to generate browsable HTML documentation.

Std.Core

36 functions. Identity, composition, boolean logic, comparison, Result and Option operations. This is the foundation everything else builds on.

Identity and composition

fn identity(x: T) -> T = x

fn compose(f: B -> C, g: A -> B) -> (A -> C) =
  fn(x) -> f(g(x))

fn pipe(x: T, f: T -> U) -> U = f(x)

fn apply(f: T -> U, x: T) -> U = f(x)

fn const(x: T) -> (U -> T) =
  fn(y) -> x

Boolean operations

fn bool_not(x: Bool) -> Bool = if x then false else true
fn bool_and(x: Bool, y: Bool) -> Bool = if x then y else false
fn bool_or(x: Bool, y: Bool) -> Bool = if x then true else y
fn bool_xor(x: Bool, y: Bool) -> Bool = if x then bool_not(y) else y

Comparison

fn eq(x: T, y: T) -> Bool = x == y
fn ne(x: T, y: T) -> Bool = x != y
fn lt(x: T, y: T) -> Bool = x < y
fn le(x: T, y: T) -> Bool = x <= y
fn gt(x: T, y: T) -> Bool = x > y
fn ge(x: T, y: T) -> Bool = x >= y
fn min(x: T, y: T) -> T = if x <= y then x else y
fn max(x: T, y: T) -> T = if x >= y then x else y
fn clamp(value: T, lo: T, hi: T) -> T =
  if value < lo then lo else if value > hi then hi else value

Result operations

Result is represented as Ok(value) -> {:ok, value} and Error(reason) -> {:error, reason}:

fn ok(value: T) -> Result(T, E) = Ok(value)
fn error(reason: E) -> Result(T, E) = Error(reason)
fn is_ok(result: Result(T, E)) -> Bool
fn is_error(result: Result(T, E)) -> Bool
fn unwrap_ok(result: Result(T, E), default: T) -> T
fn unwrap_error(result: Result(T, E), default: E) -> E
fn map_ok(result: Result(T, E), f: T -> U) -> Result(U, E)
fn map_error(result: Result(T, E), f: E -> F) -> Result(T, F)
fn and_then(result: Result(T, E), f: T -> Result(U, E)) -> Result(U, E)
fn or_else(result: Result(T, E), f: E -> Result(T, F)) -> Result(T, F)

Option operations

Option is represented as Some(value) -> {:some, value} and None() -> {:none}:

fn some(value: T) -> Option(T) = Some(value)
fn none() -> Option(T) = None()
fn is_some(opt: Option(T)) -> Bool
fn is_none(opt: Option(T)) -> Bool
fn unwrap(opt: Option(T), default: T) -> T
fn map_option(opt: Option(T), f: T -> U) -> Option(U)
fn flat_map_option(opt: Option(T), f: T -> Option(U)) -> Option(U)
fn option_or(opt: Option(T), default: T) -> T

Usage example

mod Example
  use Std.Core

  fn main() -> Atom =
    let result = ok(42)
    let doubled = map_ok(result, fn(x) -> x * 2)
    let value = unwrap_ok(doubled, 0)
    Std.Io.print_int(value)    # prints 84

Std.List

25 functions. Recursive list processing.

@extern(:erlang, :length, 1)
fn length(list: List(T)) -> Int

fn is_empty(list: List(T)) -> Bool
fn head(list: List(T), default: T) -> T
fn tail(list: List(T)) -> List(T)
fn last(list: List(T), default: T) -> T
fn nth(list: List(T), idx: Int, default: T) -> T
fn cons(elem: T, list: List(T)) -> List(T)
fn append(a: List(T), b: List(T)) -> List(T)
fn concat(lists: List(List(T))) -> List(T)
fn reverse(list: List(T)) -> List(T)
fn map(list: List(T), f: T -> U) -> List(U)
fn filter(list: List(T), pred: T -> Bool) -> List(T)
fn foldl(list: List(T), acc: U, f: T -> U -> U) -> U
fn foldr(list: List(T), acc: U, f: T -> U -> U) -> U
fn flat_map(list: List(T), f: T -> List(U)) -> List(U)
fn zip_with(a: List(T), b: List(U), f: T -> U -> V) -> List(V)
fn take(list: List(T), n: Int) -> List(T)
fn drop(list: List(T), n: Int) -> List(T)
fn contains(list: List(T), elem: T) -> Bool
fn find(list: List(T), pred: T -> Bool, default: T) -> T
fn any(list: List(T), pred: T -> Bool) -> Bool
fn all(list: List(T), pred: T -> Bool) -> Bool
fn sum(list: List(Int)) -> Int
fn product(list: List(Int)) -> Int
fn count(list: List(T), pred: T -> Bool) -> Int

Note: foldl and foldr use curried callbacks: fn(x) -> fn(acc) -> ....

Usage example

mod ListExample
  use Std.List

  fn main() -> Atom =
    let nums = [1, 2, 3, 4, 5]
    let doubled = map(nums, fn(x) -> x * 2)
    let evens = filter(doubled, fn(x) -> x % 4 == 0)
    let total = sum(evens)
    Std.Io.print_int(total)   # 4 + 8 = 12

Std.Math

18 functions. Arithmetic, trigonometric helpers, and safe division.

@extern(:erlang, :abs, 1)
fn abs(x: Int) -> Int

@extern(:math, :sqrt, 1)
fn sqrt(x: Float) -> Float

@extern(:math, :pow, 2)
fn pow(base: Float, exp: Float) -> Float

@extern(:math, :log, 1)
fn log(x: Float) -> Float

@extern(:math, :log2, 1)
fn log2(x: Float) -> Float

@extern(:math, :log10, 1)
fn log10(x: Float) -> Float

@extern(:math, :ceil, 1)
fn ceil(x: Float) -> Float

@extern(:math, :floor, 1)
fn floor(x: Float) -> Float

@extern(:erlang, :round, 1)
fn round(x: Float) -> Int

@extern(:math, :pi, 0)
fn pi() -> Float

fn max(a: Int, b: Int) -> Int
fn min(a: Int, b: Int) -> Int
fn clamp(value: Int, lo: Int, hi: Int) -> Int
fn sign(x: Int) -> Int
fn negate(x: Int) -> Int = 0 - x
fn is_even(x: Int) -> Bool = x % 2 == 0
fn is_odd(x: Int) -> Bool = x % 2 != 0
fn safe_div(a: Int, b: Int) -> Int = if b == 0 then 0 else a / b

Usage example

mod MathExample
  use Std.Math

  fn hypotenuse(a: Float, b: Float) -> Float =
    sqrt(pow(a, 2.0) + pow(b, 2.0))

  fn main() -> Atom =
    let h = hypotenuse(3.0, 4.0)
    Std.Io.print_float(h)    # 5.0

Std.String

17 functions. String manipulation via Erlang binary operations.

@extern(:erlang, :byte_size, 1)
fn length(s: String) -> Int

fn is_empty(s: String) -> Bool = s == ""
fn concat(a: String, b: String) -> String = a <> b

@extern(:string, :lowercase, 1)
fn downcase(s: String) -> String

@extern(:string, :uppercase, 1)
fn upcase(s: String) -> String

@extern(:string, :trim, 1)
fn trim(s: String) -> String

@extern(:string, :trim_leading, 1)
fn trim_leading(s: String) -> String

@extern(:string, :trim_trailing, 1)
fn trim_trailing(s: String) -> String

@extern(:erlang, :integer_to_binary, 1)
fn from_int(n: Int) -> String

@extern(:erlang, :float_to_binary, 1)
fn from_float(f: Float) -> String

@extern(:erlang, :atom_to_binary, 1)
fn from_atom(a: Atom) -> String

@extern(:erlang, :binary_to_integer, 1)
fn to_int(s: String) -> Int

@extern(:erlang, :binary_to_float, 1)
fn to_float(s: String) -> Float

@extern(:erlang, :binary_to_atom, 1)
fn to_atom(s: String) -> Atom

@extern(:binary, :split, 2)
fn split(s: String, sep: String) -> List(String)

@extern(:binary, :copy, 2)
fn repeat(s: String, n: Int) -> String

fn reverse(s: String) -> String

reverse works by converting to a charlist, reversing, and converting back.


Std.Pair

9 functions. Tuple pair operations.

@extern(:erlang, :element, 2)
fn element(index: Int, tuple: Tuple) -> T

fn first(pair: Tuple) -> T = element(1, pair)
fn second(pair: Tuple) -> T = element(2, pair)
fn swap(pair: Tuple) -> Tuple = %[second(pair), first(pair)]
fn map_first(pair: Tuple, f: A -> C) -> Tuple
fn map_second(pair: Tuple, f: B -> C) -> Tuple
fn map_both(pair: Tuple, f: A -> C, g: B -> D) -> Tuple
fn to_list(pair: Tuple) -> List(T)
fn from_list(list: List(T)) -> Tuple

Tuples are constructed with %[a, b] syntax. element uses 1-based indexing (Erlang convention).


Std.Io

8 functions. Console output.

@extern(:io, :put_chars, 1)
fn put_chars(text: String) -> Atom

fn println(text: String) -> Atom = put_chars(text <> "\n")
fn print(text: String) -> Atom = put_chars(text)

@extern(:erlang, :integer_to_binary, 1)
fn int_to_string(n: Int) -> String

@extern(:erlang, :float_to_binary, 1)
fn float_to_string(f: Float) -> String

@extern(:erlang, :atom_to_binary, 1)
fn atom_to_string(a: Atom) -> String

fn print_int(n: Int) -> Atom = println(int_to_string(n))
fn print_float(f: Float) -> Atom = println(float_to_string(f))

Std.Show

Protocol module. Defines Show(T) with show(x: T) -> String.

Implementations for Int, Float, String, Bool, Atom. Plus show_line(x) for show-with-newline. See the Protocols page for full details.

proto Show(T)
  fn show(x: T) -> String

fn show_line(x: T) -> String = show(x) <> "\n"

Std.Eq

Protocol module. Defines Eq(T) with eq(a: T, b: T) -> Bool.

Implementations for Int, Float, String, Bool, Atom. Plus ne/2.

proto Eq(T)
  fn eq(a: T, b: T) -> Bool

fn ne(a: T, b: T) -> Bool = if eq(a, b) then false else true

Std.Ord

Protocol module. Defines Ord(T) with compare(a: T, b: T) -> Atom.

Implementations for Int, Float, String, Atom. Returns :lt, :eq, or :gt.

proto Ord(T)
  fn compare(a: T, b: T) -> Atom

fn lt(a: T, b: T) -> Bool = compare(a, b) == :lt
fn le(a: T, b: T) -> Bool = compare(a, b) != :gt
fn gt(a: T, b: T) -> Bool = compare(a, b) == :gt
fn ge(a: T, b: T) -> Bool = compare(a, b) != :lt

Std.Result

11 functions. Dedicated Result module (complements Std.Core‘s result operations).

fn ok(value: T) -> Result(T, E)
fn error(reason: E) -> Result(T, E)
fn is_ok(result: Result(T, E)) -> Bool
fn is_error(result: Result(T, E)) -> Bool
fn unwrap(result: Result(T, E), default: T) -> T
fn unwrap_error(result: Result(T, E), default: E) -> E
fn map(result: Result(T, E), f: T -> U) -> Result(U, E)
fn map_error(result: Result(T, E), f: E -> F) -> Result(T, F)
fn and_then(result: Result(T, E), f: T -> Result(U, E)) -> Result(U, E)
fn or_else(result: Result(T, E), f: E -> Result(T, F)) -> Result(T, F)
fn flatten(result: Result(Result(T, E), E)) -> Result(T, E)

flatten unwraps a nested Result. and_then is monadic bind. or_else handles the error path.


Std.Option

10 functions. Optional values.

fn some(value: T) -> Option(T)
fn none() -> Option(T)
fn is_some(opt: Option(T)) -> Bool
fn is_none(opt: Option(T)) -> Bool
fn unwrap(opt: Option(T), default: T) -> T
fn map(opt: Option(T), f: T -> U) -> Option(U)
fn flat_map(opt: Option(T), f: T -> Option(U)) -> Option(U)
fn or_else(opt: Option(T), default: T) -> T
fn filter(opt: Option(T), pred: T -> Bool) -> Option(T)
fn zip(a: Option(T), b: Option(U)) -> Option(Tuple)

zip combines two Options into an Option of a pair. Both must be Some for the result to be Some.


Std.Map

14 functions. Backed by Erlang’s :maps module.

fn new() -> Map
fn put(key: K, value: V, map: Map) -> Map
fn get(key: K, map: Map) -> V
fn get_or(key: K, map: Map, default: V) -> V
fn remove(key: K, map: Map) -> Map
fn has_key(key: K, map: Map) -> Bool
fn size(map: Map) -> Int
fn keys(map: Map) -> List(K)
fn values(map: Map) -> List(V)
fn to_list(map: Map) -> List(Tuple)
fn from_list(list: List(Tuple)) -> Map
fn merge(map1: Map, map2: Map) -> Map
fn is_empty(map: Map) -> Bool = size(map) == 0
fn count(map: Map) -> Int = size(map)

Note the argument order: put(key, value, map) follows Erlang’s :maps.put/3 convention, not the map |> put(key, value) style.


Std.Set

11 functions. Sets represented as maps with :true values.

fn new() -> Map = Std.Map.new()
fn add(elem: T, set: Map) -> Map
fn remove(elem: T, set: Map) -> Map
fn member(elem: T, set: Map) -> Bool
fn size(set: Map) -> Int
fn is_empty(set: Map) -> Bool
fn to_list(set: Map) -> List(T)
fn from_list(list: List(T)) -> Map
fn union(a: Map, b: Map) -> Map
fn intersection(a: Map, b: Map) -> Map
fn difference(a: Map, b: Map) -> Map

intersection and difference are implemented by folding over the first set and checking membership in the second.


Std.Functor

Protocol module. Defines Functor(F) with fmap(container: F, f: A -> B) -> F.

Currently implemented for List only.

proto Functor(F)
  fn fmap(container: F, f: A -> B) -> F

Std.System

10 functions. System information and time.

@extern(:erlang, :system_time, 0)
fn monotonic_time() -> Int

@extern(:os, :system_time, 1)
fn system_time(unit: Atom) -> Int

fn timestamp_ms() -> Int = system_time(:millisecond)
fn timestamp_us() -> Int = system_time(:microsecond)

@extern(:erlang, :self, 0)
fn self() -> Pid

@extern(:erlang, :node, 0)
fn node() -> Atom

@extern(:erlang, :system_info, 1)
fn system_info(key: Atom) -> T

fn otp_version() -> T = system_info(:otp_release)
fn cpu_count() -> Int = system_info(:logical_processors)

@extern(:erlang, :halt, 1)
fn exit(code: Int) -> Atom

Std.Fsm

12 functions. Runtime interface for spawning and interacting with compiled FSM instances (gen_statem processes).

@extern(Elixir.Cure.FSM.Builtins, :fsm_spawn, 1)
fn spawn(fsm_module: Atom) -> Pid       # e.g. spawn(:"Cure.FSM.TrafficLight")

@extern(Elixir.Cure.FSM.Builtins, :fsm_spawn_named, 2)
fn spawn_named(fsm_module: Atom, name: String) -> Pid

fn stop(pid: Pid) -> Atom
fn send(pid: Pid, event: Atom) -> Atom
fn send_batch(pid: Pid, events: List(Atom)) -> Atom
fn get_state(pid: Pid) -> Tuple
fn state(pid: Pid) -> Atom
fn is_alive(pid: Pid) -> Bool
fn info(pid: Pid) -> Map
fn history(pid: Pid) -> List(Atom)
fn lookup(name: String) -> Pid

Usage example

mod FsmExample
  use Std.Fsm

  fn main() -> Atom =
    let pid = spawn(:"Cure.FSM.TrafficLight")
    send(pid, :timer)
    let current = state(pid)
    Std.Io.println(Std.String.from_atom(current))
    stop(pid)

Std.Vector

Length-indexed vectors with dependent types. Vectors are represented as tagged tuples {:vector, length, list} where the length is tracked at the type level.

fn empty() -> Tuple = %[:vector, 0, []]
fn singleton(x: T) -> Tuple = %[:vector, 1, [x]]
fn from_list(list: List(T)) -> Tuple
fn to_list(vec: Tuple) -> List(T)
fn length(vec: Tuple) -> Int
fn cons(x: T, vec: Tuple) -> Tuple        # length increments by 1
fn head(vec: Tuple) -> T                   # requires length > 0
fn tail(vec: Tuple) -> Tuple
fn append(a: Tuple, b: Tuple) -> Tuple     # length = len(a) + len(b)
fn map(vec: Tuple, f: T -> U) -> Tuple     # preserves length
fn is_empty(vec: Tuple) -> Bool

cons increments the tracked length. append sums the lengths of both vectors. head on a zero-length vector returns the default value 0 – a future version will enforce n > 0 at the type level via dependent types.


Std.Test

5 functions. Basic assertion primitives for the cure test command.

fn assert(condition: Bool) -> Atom
fn assert_eq(a: T, b: T) -> Atom
fn assert_ne(a: T, b: T) -> Atom
fn assert_gt(a: T, b: T) -> Atom
fn assert_lt(a: T, b: T) -> Atom

All assertion functions return :ok on success or raise an error on failure. Test files go in test/ with function names starting with test:

mod MyTests
  use Std.Test

  fn test_addition() -> Atom =
    assert_eq(1 + 1, 2)

  fn test_list_length() -> Atom =
    assert_eq(Std.List.length([1, 2, 3]), 3)

Run with cure test.