← Back to Documentation

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
end

Exports

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 comment

3. 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 | Gt

Record Types

Records define structured data with named fields:

record RecordName do
  field1: Type1
  field2: Type2
  field3: Type3
end

Record with Type Parameters

record Point(T) do
  x: T
  y: T
end

Record 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"
end

4. Function Definitions

Basic Function Syntax

def function_name(param1: Type1, param2: Type2): ReturnType =
  expression

Function with Match Expression

def length(list: List(T)): Nat =
  match list do
    [] -> Zero
    [_ | t] -> Succ(length(t))
  end

Functions 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
  end

Lambda 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 end

Type 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 signature

Nested 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 8

Lambdas 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
end

Limitations

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 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.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


5. Pattern Matching

Match Expression Structure

match value do
  pattern1 -> result1
  pattern2 -> result2
  _ -> default_result
end

Pattern 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"
end

List Patterns

match list do
  [] -> # empty list
  [h | t] -> # head and tail
  [a, b, c] -> # exact length (if supported)
end

Constructor Patterns

match result do
  Ok(value) -> # success case
  Error(err) -> # error case
end

match option do
  Some(value) -> # present
  None -> # absent
end

Nested Match

match list do
  [] -> []
  [h | t] ->
    match predicate(h) do
      true -> [h | filtered_tail]
      false -> filtered_tail
    end
end

6. 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 end

Polymorphic 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      # Atom

Lists

[]              # Empty list
[1, 2, 3]       # List of integers
[h | t]         # Cons pattern/constructor

Tuples

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
end

Nested 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"
end

Tuples 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"
end

Tuples 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
  end

Returning 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}
  end

Tuple 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"
end

Mixed 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"
end

8. 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 equal

List Construction

[element | list]    # Cons (prepend)

String Concatenation

str1 <> str2    # Diamond operator for string concatenation

9. Let Bindings

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

10. 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 n

Example:

def length(list: List(T)): Nat =
  match list do
    [] -> Zero
    [_ | t] -> Succ(length(t))
  end

11. 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
end

FSM 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
end

FSM 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
end

Recursive List Processing

def map(list: List(T), f: T -> U): List(U) =
  match list do
    [] -> []
    [h | t] -> [f(h) | map(t, f)]
  end

Tail Recursion with Accumulator

def reverse(list: List(T), acc: List(T)): List(T) =
  match list do
    [] -> acc
    [h | t] -> reverse(t, [h | acc])
  end

Curried Functions

# Function returns another function
def flip(f: A -> B -> C): B -> A -> C =
  fn(b, a) -> 
    let g = f(a) g(b)
  end

13. Key Syntactic Rules

  1. Module Required: Every file needs a module ModuleName do ... end
  2. Type Annotations: All function parameters and return types must be annotated
  3. Pattern Matching: Use match ... do ... end blocks
  4. Lambdas: Use fn(params) -> body end syntax
  5. Let Binding: Simple let name = value without semicolons
  6. Function Application: Standard func(arg1, arg2) syntax
  7. Comments: Only # single-line comments
  8. Indentation: Not significant (use do ... end blocks)

14. Things to AVOID (Not in Std Library)

Based on actual standard library, these features may not be implemented:

  1. Process definitions: process name(...) do ... end - verify syntax
  2. Dependent types: Vector(T, n: Nat) - may not fully work yet
  3. String interpolation: "text #{expr}" - use <> for concatenation instead
  4. If-then-else: May exist but std lib uses match expressions

When creating examples:

  1. Start with module definition
  2. Import needed functions from Std library
  3. Define types (sum types with constructors)
  4. Define functions with explicit type signatures
  5. Use pattern matching for control flow
  6. Use lambdas for higher-order functions
  7. 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
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.