Based on: Actual Cure implementation (Standard Library v1.0)
Purpose: Reference for creating correct Cure examples
Last Updated: November 22, 2025
Every Cure file should define a module:
module ModuleName do
export [function_name/1, TypeName]
# Module contents
end
Export declarations specify what's public:
export [
function_name/1, # Function with arity
TypeName, # Type constructor
constructor_name # Data constructor
]
Import from other modules (including standard library):
import Std.Core [Result, Option, ok/1, error/1]
import Std.List [map/2, filter/2, fold/3]
import Std.Io [print/1, println/1]
Single-line comments only:
# This is a comment
type Result(T, E) = Ok(T) | Error(E)
type Option(T) = Some(T) | None
type Ordering = Lt | Eq | Gt
Records define structured data with named fields:
record RecordName do
field1: Type1
field2: Type2
field3: Type3
end
record Point(T) do
x: T
y: T
end
# Create a record instance
let point = Point{x: 3.0, y: 4.0}
let payload = TrafficPayload{cycles_completed: 0, timer_events: 0, emergency_stops: 0}
# Match and extract fields
match point do
Point{x: x, y: y} when x == 0.0 and y == 0.0 ->
"Origin"
Point{x: x, y: y} when x > 0.0 and y > 0.0 ->
"First quadrant"
Point{x: _, y: y} when y == 0.0 ->
"On X-axis"
end
# Partial field matching (other fields ignored)
match person do
Person{age: age, score: score} when age < 18 and score >= 90 ->
"Outstanding young achiever"
end
def function_name(param1: Type1, param2: Type2): ReturnType =
expression
def length(list: List(T)): Nat =
match list do
[] -> Zero
[_ | t] -> Succ(length(t))
end
def filter(list: List(T), predicate: T -> Bool): List(T) =
match list do
[] -> []
[h | t] ->
let filtered_tail = filter(t, predicate)
match predicate(h) do
true -> [h | filtered_tail]
false -> filtered_tail
end
end
Lambdas (anonymous functions) allow inline function definitions without naming them.
Syntax: fn(params) -> expression end
# Simple lambda (one parameter)
let double = fn(x) -> x * 2 end
# Lambda with multiple parameters
let add = fn(x, y) -> x + y end
# Zero-parameter lambda (thunk)
let get_constant = fn() -> 42 end
Lambda parameter types are inferred from context - you don't need to annotate them:
# Type inferred from list element type
let doubled = map([1, 2, 3], fn(x) -> x * 2 end)
# x is inferred to be Int
# Type inferred from function signature
def apply_twice(f: Int -> Int, x: Int): Int =
f(f(x))
let result = apply_twice(fn(n) -> n + 1 end, 5)
# n is inferred to be Int from apply_twice signature
Lambdas can return other lambdas for currying:
# Manual currying
let add = fn(x) -> fn(y) -> x + y end end
# Partial application (conceptually)
# let add_five = add(5) # Returns fn(y) -> 5 + y end
# let result = add_five(3) # Returns 8
# Real example with fold
let sum = fold([1, 2, 3, 4], 0, fn(x) -> fn(acc) -> x + acc end end)
Lambdas can capture variables from their surrounding scope:
# Capture from outer scope
let base = 10
let add_base = fn(x) -> x + base end
# add_base captures 'base' (closure)
let result = add_base(5) # Returns 15
# Multiple captures
def make_adder(x: Int): Int -> Int =
fn(y) -> x + y end
# Returns a lambda that captures x
let add_five = make_adder(5)
let result = add_five(3) # Returns 8
Most common use is passing lambdas to higher-order functions:
import Std.List [map/2, filter/2, fold/3]
# Map: transform each element
let doubled = map([1, 2, 3, 4, 5], fn(x) -> x * 2 end)
# Result: [2, 4, 6, 8, 10]
# Filter: select elements
let evens = filter([1, 2, 3, 4, 5], fn(x) -> x % 2 == 0 end)
# Result: [2, 4]
# Fold: aggregate with accumulator
let sum = fold([1, 2, 3, 4, 5], 0,
fn(x) -> fn(acc) -> x + acc end end)
# Result: 15
# Chain operations
let result =
[1, 2, 3, 4, 5]
|> map(fn(x) -> x * 2 end)
|> filter(fn(x) -> x > 5 end)
|> fold(0, fn(x) -> fn(acc) -> x + acc end end)
Lambda bodies can contain any expression, including pattern matching:
# Lambda with match expression
let classify = fn(x) ->
match x do
n when n > 0 -> "positive"
0 -> "zero"
_ -> "negative"
end
end
# Lambda with let bindings
let compute = fn(x) ->
let doubled = x * 2
let squared = doubled * doubled
squared + 1
end
Recursive Lambdas: Lambdas cannot directly call themselves (they're anonymous).
Use named functions for recursion:
# ❌ Won't work - lambda can't reference itself
let factorial = fn(n) ->
match n do
0 -> 1
_ -> n * factorial(n - 1) # Error: factorial undefined
end
end
# ✅ Use named function instead
def factorial(n: Nat): Nat =
match n do
Zero -> Succ(Zero)
Succ(pred) -> mult(n, factorial(pred))
end
Direct Invocation: Directly calling a lambda literal may require binding first:
# May not work:
# (fn(x) -> x + 1 end)(5)
# Instead, bind to variable:
let increment = fn(x) -> x + 1 end
let result = increment(5) # Works
Function-level guards use when clauses to specify preconditions:
# Basic guard
def is_positive(x: Int): Bool when x > 0 = true
def is_positive(_x: Int): Bool = false
# Multi-clause with guards
def abs(x: Int): Int when x >= 0 = x
def abs(x: Int): Int = 0 - x
# Sign function with complete coverage
def sign(x: Int): Int when x > 0 = 1
def sign(x: Int): Int when x == 0 = 0
def sign(x: Int): Int = -1
# Guards with AND
def in_range(x: Int, min: Int, max: Int): Bool
when x >= min and x <= max = true
def in_range(_x: Int, _min: Int, _max: Int): Bool = false
# Guards with OR
def is_extreme(x: Int): Bool
when x > 100 or x < -100 = true
def is_extreme(_x: Int): Bool = false
# Real-world example: tax brackets
def tax_rate(income: Int): Float when income <= 10000 = 0.0
def tax_rate(income: Int): Float
when income > 10000 and income <= 40000 = 0.1
def tax_rate(income: Int): Float
when income > 40000 and income <= 100000 = 0.2
def tax_rate(_income: Int): Float = 0.3
Guard Features:
- Comparison operators: >, <, >=, <=, ==, !=
- Logical operators: and, or
- Type refinement: Guards narrow types in function bodies
- SMT verification: Completeness and consistency checking
- Coverage analysis: Detects unreachable clauses
See: examples/06_comprehensive_guards_demo.cure for complete examples
match value do
pattern1 -> result1
pattern2 -> result2
_ -> default_result
end
Guards allow additional conditions on patterns using when:
match value do
x when x < 0 -> "Negative"
x when x == 0 -> "Zero"
x when x > 0 -> "Positive"
end
# Multiple conditions with logical operators
match n do
x when x >= 10 and x <= 20 -> "In range"
x when x < 10 or x > 20 -> "Out of range"
end
# Guards with record patterns
match point do
Point{x: x, y: y} when x > 0.0 and y > 0.0 ->
"First quadrant"
Point{x: x, y: _} when x == 0.0 ->
"On Y-axis"
end
match list do
[] -> # empty list
[h | t] -> # head and tail
[a, b, c] -> # exact length (if supported)
end
match result do
Ok(value) -> # success case
Error(err) -> # error case
end
match option do
Some(value) -> # present
None -> # absent
end
match list do
[] -> []
[h | t] ->
match predicate(h) do
true -> [h | filtered_tail]
false -> filtered_tail
end
end
Arrow notation for function types:
# Function taking T, returning U
T -> U
# Function taking two params
T -> U -> V
# Can be curried
def func(x: T): U -> V =
fn(y) -> result end
Generic type parameters:
def identity(x: T): T = x
def map(list: List(T), f: T -> U): List(U) = ...
42 # Int
3.14 # Float
"hello" # String
true # Bool
false # Bool
:atom_name # Atom
[] # Empty list
[1, 2, 3] # List of integers
[h | t] # Cons pattern/constructor
Tuples group multiple values of potentially different types together.
Syntax: {elem1, elem2, ...}
# Empty tuple
let empty = {}
# Two-element tuple (pair)
let pair = {10, 20}
let point = {3.0, 4.0}
# Three-element tuple
let triple = {1, "hello", true}
# Nested tuples
let nested = {{1, 2}, {3, 4}}
# Mixed with other types
let mixed = {[1, 2, 3], "numbers", Ok(42)}
Tuples can be destructured in match expressions:
# Match on tuple
let point = {5, 10}
match point do
{0, 0} -> "Origin"
{x, 0} -> "On X-axis"
{0, y} -> "On Y-axis"
{x, y} -> "General point"
end
# Match with literals
match tuple do
{0, 0} -> "Both zero"
{1, 2} -> "Exact match"
{x, y} -> "Any other pair"
end
# Match with wildcards
match {1, 2, 3, 4, 5} do
{first, _, _, _, last} -> # Only care about first and last
end
# Match nested tuples
let nested = {{1, 2}, {3, 4}}
match nested do
{{a, b}, {c, d}} -> a + b + c + d
end
# Deeply nested
let deep = {1, {2, {3, 4}}}
match deep do
{x, {y, {z, w}}} -> "All extracted"
end
# Classify points by quadrant
match {x, y} do
{x, y} when x == 0 and y == 0 -> "origin"
{x, y} when x > 0 and y > 0 -> "quadrant-1"
{x, y} when x < 0 and y > 0 -> "quadrant-2"
{x, y} when x < 0 and y < 0 -> "quadrant-3"
{x, y} when x > 0 and y < 0 -> "quadrant-4"
{x, 0} -> "x-axis"
{0, y} -> "y-axis"
end
# Function taking a tuple
def distance(point: {Int, Int}): Int =
match point do
{x, y} -> x * x + y * y
end
# Multiple tuple parameters
def distance_between(p1: {Int, Int}, p2: {Int, Int}): Int =
match {p1, p2} do
{{x1, y1}, {x2, y2}} ->
let dx = x2 - x1
let dy = y2 - y1
dx * dx + dy * dy
end
Tuples are useful for returning multiple values:
# Return quotient and remainder
def divide_with_remainder(a: Int, b: Int): {Int, Int} =
{a / b, a % b}
# Return min and max
def min_max(a: Int, b: Int): {Int, Int} =
match {a, b} do
{x, y} when x < y -> {x, y}
{x, y} -> {y, x}
end
# Direct destructuring
let point = {100, 200}
let {x, y} = point
# Now x = 100, y = 200
# Works with function results
let {quot, rem} = divide_with_remainder(17, 5)
# Tuple inside Result/Option
let result = Ok({42, "success"})
match result do
Ok({value, message}) -> "Got value and message"
Error(e) -> "Error"
end
# Multiple values in constructor
let data = Some({"Alice", 30, true})
match data do
Some({name, age, active}) -> "Person data"
None -> "No data"
end
# Tuple with list
match {[1, 2, 3], "label"} do
{[], _} -> "Empty list"
{[h | t], label} -> "Non-empty list"
end
# Tuple with record
match {Point{x: 0, y: 0}, "origin"} do
{Point{x: 0, y: 0}, label} -> label
_ -> "Other"
end
x + y # Addition
x - y # Subtraction
x * y # Multiplication
x / y # Division (may be integer division)
x == y # Equality
x != y # Inequality
x < y # Less than
x > y # Greater than
x <= y # Less than or equal
x >= y # Greater than or equal
[element | list] # Cons (prepend)
str1 <> str2 # Diamond operator for string concatenation
Simple let syntax:
let variable = expression body_expression
let result = function_call() result + 10
The body expression follows the binding immediately without requiring an explicit in keyword.
Example:
let x = 5 x + 10
Declare Erlang FFI functions:
curify function_name(param: Type): ReturnType = {module, function, arity}
Example:
curify print_raw(format: String, args: List(String)): Unit = {io, format, 2}
Natural numbers use Peano encoding:
Zero # Zero
Succ(n) # Successor of n
Example:
def length(list: List(T)): Nat =
match list do
[] -> Zero
[_ | t] -> Succ(length(t))
end
FSMs are defined with an initial payload record and transition arrows:
# Define a payload record for FSM state tracking
record PayloadName do
field1: Type1
field2: Type2
end
# FSM definition with initial payload values
fsm PayloadName{field1: value1, field2: value2} do
State1 --> |event1| State2
State1 --> |event2| State1
State2 --> |event3| State1
end
record TurnstilePayload do
coins_inserted: Int
people_passed: Int
denied_attempts: Int
end
fsm TurnstilePayload{coins_inserted: 0, people_passed: 0, denied_attempts: 0} do
Locked --> |coin| Unlocked
Locked --> |push| Locked
Unlocked --> |coin| Unlocked
Unlocked --> |push| Locked
end
import Std.Fsm [fsm_spawn/2, fsm_cast/2, fsm_advertise/2, fsm_state/1]
import Std.Pair [pair/2]
# Spawn an FSM instance
let fsm_pid = fsm_spawn(:PayloadName, initial_data)
# Give it a name
let adv_result = fsm_advertise(fsm_pid, :fsm_name)
# Send an event (with empty data list)
let empty_list = []
let event = pair(:event_name, empty_list)
let cast_result = fsm_cast(:fsm_name, event)
# Get current state
let current_state = fsm_state(:fsm_name)
Key Points:
- First state in transitions is the initial state
- Events are atoms (:event_name)
- Transitions use --> and |event| syntax
- Must define a payload record even if empty
match result do
Ok(value) -> # handle success
Error(err) -> # handle error
end
match option do
Some(value) -> # handle present value
None -> # handle absence
end
def map(list: List(T), f: T -> U): List(U) =
match list do
[] -> []
[h | t] -> [f(h) | map(t, f)]
end
def reverse(list: List(T), acc: List(T)): List(T) =
match list do
[] -> acc
[h | t] -> reverse(t, [h | acc])
end
# Function returns another function
def flip(f: A -> B -> C): B -> A -> C =
fn(b, a) ->
let g = f(a) g(b)
end
module ModuleName do ... endmatch ... do ... end blocksfn(params) -> body end syntaxlet name = value without semicolonsfunc(arg1, arg2) syntax# single-line commentsdo ... end blocks)Based on actual standard library, these features may not be implemented:
process name(...) do ... end - verify syntaxVector(T, n: Nat) - may not fully work yet"text #{expr}" - use <> for concatenation insteadWhen creating examples:
module ExampleModule do
export [
main/0,
helper_function/1
]
# Import standard library functions
import Std.Core [Result, ok/1, error/1]
import Std.List [map/2, filter/2]
import Std.Io [print/1]
# Type definition
type MyType(T) = Constructor1(T) | Constructor2(String)
# Main function
def main(): Unit =
let result = helper_function(42)
print("Done")
# Helper with pattern matching
def helper_function(value: Int): Result(String, String) =
match value > 0 do
true -> ok("positive")
false -> error("non-positive")
end
end
This guide is based on actual working code in the Cure standard library. When in doubt, refer to lib/std/ directory for real examples.