View Source Cure Language Syntax Guide
Based on: Actual Cure implementation (Standard Library v1.0)
Purpose: Reference for creating correct Cure examples
Last Updated: November 22, 2025
1. Module Structure
Every Cure file should define a module:
module ModuleName do
export [function_name/1, TypeName]
# Module contents
endExports
Export declarations specify what's public:
export [
function_name/1, # Function with arity
TypeName, # Type constructor
constructor_name # Data constructor
]Imports
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]2. Comments
Single-line comments only:
# This is a comment3. Type Definitions
Type Aliases with Sum Types
type Result(T, E) = Ok(T) | Error(E)
type Option(T) = Some(T) | None
type Ordering = Lt | Eq | GtRecord Types
Records define structured data with named fields:
record RecordName do
field1: Type1
field2: Type2
field3: Type3
endRecord with Type Parameters
record Point(T) do
x: T
y: T
endRecord Construction
# 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}Record Pattern Matching
# 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"
end4. Function Definitions
Basic Function Syntax
def function_name(param1: Type1, param2: Type2): ReturnType =
expressionFunction with Match Expression
def length(list: List(T)): Nat =
match list do
[] -> Zero
[_ | t] -> Succ(length(t))
endFunctions with Let Bindings
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
endLambda Functions
Lambdas (anonymous functions) allow inline function definitions without naming them.
Syntax: fn(params) -> expression end
Basic Lambda Expressions
# 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 endType Inference
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 signatureNested Lambdas (Currying)
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)Closures (Variable Capture)
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 8Lambdas in Higher-Order Functions
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)Complex Lambda Bodies
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
endLimitations
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))
endDirect 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) # WorksFunction Guards ✅
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.3Guard 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
5. Pattern Matching
Match Expression Structure
match value do
pattern1 -> result1
pattern2 -> result2
_ -> default_result
endPattern Guards
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"
endList Patterns
match list do
[] -> # empty list
[h | t] -> # head and tail
[a, b, c] -> # exact length (if supported)
endConstructor Patterns
match result do
Ok(value) -> # success case
Error(err) -> # error case
end
match option do
Some(value) -> # present
None -> # absent
endNested Match
match list do
[] -> []
[h | t] ->
match predicate(h) do
true -> [h | filtered_tail]
false -> filtered_tail
end
end6. Type Annotations
Function Types
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 endPolymorphic Types
Generic type parameters:
def identity(x: T): T = x
def map(list: List(T), f: T -> U): List(U) = ...7. Literals and Basic Types
Primitives
42 # Int
3.14 # Float
"hello" # String
true # Bool
false # Bool
:atom_name # AtomLists
[] # Empty list
[1, 2, 3] # List of integers
[h | t] # Cons pattern/constructorTuples
Tuples group multiple values of potentially different types together.
Syntax: {elem1, elem2, ...}
Creating Tuples
# 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)}Tuple Pattern Matching
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
endNested Tuple Patterns
# 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"
endTuples with Guards
# 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"
endTuples in Function Parameters
# 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
endReturning Multiple Values
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}
endTuple Destructuring in Let
# 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)Tuples with Constructors
# 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"
endMixed Patterns
# 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"
end8. Operators
Arithmetic
x + y # Addition
x - y # Subtraction
x * y # Multiplication
x / y # Division (may be integer division)Comparison
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 equalList Construction
[element | list] # Cons (prepend)String Concatenation
str1 <> str2 # Diamond operator for string concatenation9. Let Bindings
Simple let syntax:
let variable = expression body_expression
let result = function_call() result + 10The body expression follows the binding immediately without requiring an explicit in keyword.
Example:
let x = 5 x + 1010. Special Constructs
Curify (Erlang FFI)
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}Nat Type and Peano Numbers
Natural numbers use Peano encoding:
Zero # Zero
Succ(n) # Successor of nExample:
def length(list: List(T)): Nat =
match list do
[] -> Zero
[_ | t] -> Succ(length(t))
end11. FSM Syntax
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
endFSM Example: Turnstile
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
endFSM Runtime Operations
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
12. Common Patterns from Std Library
Result/Option Handling
match result do
Ok(value) -> # handle success
Error(err) -> # handle error
end
match option do
Some(value) -> # handle present value
None -> # handle absence
endRecursive List Processing
def map(list: List(T), f: T -> U): List(U) =
match list do
[] -> []
[h | t] -> [f(h) | map(t, f)]
endTail Recursion with Accumulator
def reverse(list: List(T), acc: List(T)): List(T) =
match list do
[] -> acc
[h | t] -> reverse(t, [h | acc])
endCurried Functions
# Function returns another function
def flip(f: A -> B -> C): B -> A -> C =
fn(b, a) ->
let g = f(a) g(b)
end13. Key Syntactic Rules
- Module Required: Every file needs a
module ModuleName do ... end - Type Annotations: All function parameters and return types must be annotated
- Pattern Matching: Use
match ... do ... endblocks - Lambdas: Use
fn(params) -> body endsyntax - Let Binding: Simple
let name = valuewithout semicolons - Function Application: Standard
func(arg1, arg2)syntax - Comments: Only
#single-line comments - Indentation: Not significant (use
do ... endblocks)
14. Things to AVOID (Not in Std Library)
Based on actual standard library, these features may not be implemented:
- Process definitions:
process name(...) do ... end- verify syntax - Dependent types:
Vector(T, n: Nat)- may not fully work yet - String interpolation:
"text #{expr}"- use<>for concatenation instead - If-then-else: May exist but std lib uses match expressions
15. Recommended Workflow
When creating examples:
- Start with module definition
- Import needed functions from Std library
- Define types (sum types with constructors)
- Define functions with explicit type signatures
- Use pattern matching for control flow
- Use lambdas for higher-order functions
- Keep it simple - mirror std library style
Example: Complete Module Template
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
endThis guide is based on actual working code in the Cure standard library. When in doubt, refer to lib/std/ directory for real examples.