Examples

Runnable programs in examples/ that showcase March language features and common patterns. Each example is a self-contained .march file.

To run any example:

dune exec march -- examples/<name>.march

Fundamentals

hello.march

Functions, let bindings, recursion, and |> pipes with the List stdlib. A good first program.

fn greet(name : String) : String do
  "Hello, " ++ name ++ "!"
end

fn factorial(0) : Int do 1 end
fn factorial(n) : Int do n * factorial(n - 1) end

fn double_evens(xs : List(Int)) : List(Int) do
  xs
  |> List.filter(fn x -> x % 2 == 0)
  |> List.map(fn x -> x * 2)
end

pattern_matching.march

Comprehensive pattern matching: literals, custom ADTs, Option/Result, nested patterns, and multi-head function definitions.

type Shape = Circle(Int) | Rect(Int, Int) | Triangle(Int, Int)

fn area(s : Shape) : Int do
  match s do
    Circle(r)      -> r * r * 3
    Rect(w, h)     -> w * h
    Triangle(b, h) -> (b * h) / 2
  end
end

-- Nested Option patterns
fn unwrap_nested(v : Option(Option(Int))) : Int do
  match v do
    Some(Some(n)) -> n
    Some(None)    -> -1
    None          -> -2
  end
end

list_lib.march

A custom integer list type built with recursive ADTs. Implements length, sum, product, max, reverse, append, and range from scratch — a clean exercise in pattern matching and recursion.

type IntList = INil | ICons(Int, IntList)

fn sum(INil)         : Int do 0 end
fn sum(ICons(x, xs)) : Int do x + sum(xs) end

fn reverse(xs : IntList) : IntList do rev_acc(xs, INil) end
fn rev_acc(INil, acc)         do acc end
fn rev_acc(ICons(x, xs), acc) do rev_acc(xs, ICons(x, acc)) end

modules.march

Module organisation in one file: qualified access, two-level nesting, and private functions (pfn).

-- Qualified access
let a = MathUtils.square(4)

-- Two-level nesting
let area = Geometry.Rect.area(6, 7)

-- pfn is private  only callable within Crypto
mod Crypto do
  fn encode(x : Int) : Int do add_checksum(scramble(x)) end
  pfn scramble(x : Int) : Int do x * 31 + 7 end
end

Actors & Concurrency

actors.march

Spawning actors, sending messages, handling death gracefully, and observing state transitions.

actor Counter do
  state { value : Int }
  init  { value = 0 }

  on Increment(n : Int) do { state with value = state.value + n } end
  on Reset()            do { state with value = 0 } end
  on Probe(label : String) do
    println("[Counter] " ++ label ++ " = " ++ int_to_string(state.value))
    state
  end
end

let c = spawn(Counter)
send(c, Increment(5))
send(c, Probe("after increment"))

tasks_basic.march

Lightweight tasks: spawn, await, chained results, and fan-out patterns using the Collatz sequence as a stand-in for CPU-bound work.

-- Two independent tasks, combined on await
fn two_tasks() : Int do
  let t1 = task_spawn(fn x -> collatz(27, 0))
  let t2 = task_spawn(fn x -> collatz(871, 0))
  task_await_unwrap(t1) + task_await_unwrap(t2)
end

tasks_fork_join.march

Recursive divide-and-conquer parallelism. Forks at a midpoint, spawns tasks for each half, and joins results. Demonstrates the canonical pattern for tree-structured parallel work.

fn par_sum(lo : Int, hi : Int, threshold : Int) : Int do
  if hi - lo <= threshold do sum_range(lo, hi)
  else do
    let mid   = lo + (hi - lo) / 2
    let left  = task_spawn(fn x -> par_sum(lo, mid, threshold))
    let right = task_spawn(fn x -> par_sum(mid + 1, hi, threshold))
    task_await_unwrap(left) + task_await_unwrap(right)
  end
end

Supervision

supervision_strategies.march

All three restart strategies in one file: one_for_one, one_for_all, and rest_for_one. Each is demonstrated with a concrete crash scenario so you can observe which siblings are restarted.

-- one_for_one: only the crashed child restarts
actor OneForOneSup do
  state { wa : Int, wb : Int }
  init  { wa = 0, wb = 0 }
  supervise do
    strategy one_for_one
    max_restarts 5 within 60
    Worker wa
    Worker wb
  end
end

-- rest_for_one: crash parser  restarts parser + writer, not reader
actor PipelineSup do
  state { reader : Int, parser : Int, writer : Int }
  init  { reader = 0, parser = 0, writer = 0 }
  supervise do
    strategy rest_for_one
    max_restarts 5 within 60
    Worker reader
    Worker parser
    Worker writer
  end
end

supervision_monitor.march

Actor monitoring and Down messages. Shows how to watch for actor death, handle the Down delivery in a mailbox, and demonitor cleanly.


supervision_linear_drop.march

Resource cleanup with user-defined Drop implementations. Resources registered via own() are released in reverse acquisition order when their actor crashes — the RAII pattern for actors.

impl Drop(DbConnection) do
  fn drop(conn) do
    match conn do
    DbConnection(id) -> println("[Drop] Closing db connection " ++ to_string(id))
    end
  end
end

-- Register with an actor  dropped automatically on crash
own(pid, DbConnection(100))
own(pid, FileHandle("/var/log/worker.log"))
-- On crash: FileHandle dropped first, then DbConnection

HTTP & Web

http_hello.march

The smallest possible HTTP server: 18 lines, one route, plain text response.

fn router(conn) do
  match (HttpServer.method(conn), HttpServer.path_info(conn)) do
  (:get, Nil) -> conn |> HttpServer.text(200, "Hello from compiled March!")
  _ -> conn |> HttpServer.text(404, "Not Found")
  end
end

fn main() do
  HttpServer.new(8080)
  |> HttpServer.plug(router)
  |> HttpServer.listen()
end

http_streaming.march

Three streaming patterns: print chunks as they arrive, byte counting without buffering, and large chunked transfer encoding. Uses HttpClient.stream_get with an on_chunk callback.

fn print_chunk(chunk) do
  print("[chunk " ++ int_to_string(string_length(chunk)) ++ " bytes]")
  print(chunk)
end

match HttpClient.stream_get(client, "http://httpbin.org/stream/5", print_chunk) do
Ok((status, _, _)) -> print("Status: " ++ int_to_string(status))
Err(_)             -> print("Error!")
end

http_requests.march

Full HTTP client stack: GET and POST requests, request pipeline steps, default headers, and Result-based error handling.


http_test.march

Integration test pattern: spawn a server subprocess, make real HTTP requests (GET /, GET /ping, POST /echo), assert on responses.


counter_server.march

An actor-backed HTTP API. A Counter actor holds state; HTTP routes GET /count, POST /increment, POST /decrement message it directly. Shows the standard actor+HTTP integration pattern.


ws_echo.march

WebSocket server on port 9877. Handles TextFrame, BinaryFrame, Ping/Pong, and Close in a recursive loop.

fn handle_frame(conn, frame) do
  match frame do
  TextFrame(msg)   -> WsServer.send_text(conn, "echo: " ++ msg)
  BinaryFrame(b)   -> WsServer.send_binary(conn, b)
  Ping(data)       -> WsServer.send_pong(conn, data)
  Close            -> WsServer.close(conn)
  end
end

Data & Statistics

stats_basic.march

Descriptive and bivariate statistics using the Stats module on plain List(Float) values.

-- Weekly temperatures
let temps = [18.5, 21.0, 19.8, 23.4, 22.1, 17.6, 20.3]

println("mean:    " ++ float_to_string(Stats.mean(temps)))
println("median:  " ++ float_to_string(Stats.median(temps)))
println("std_dev: " ++ float_to_string(Stats.std_dev(temps)))
println("p25:     " ++ float_to_string(Stats.percentile(temps, 25.0)))
println("p75:     " ++ float_to_string(Stats.percentile(temps, 75.0)))

-- Linear regression: hours studied  exam score
let (slope, intercept) = Stats.linear_regression(hours, scores)
let predicted = slope *. 9.0 +. intercept

Also shows covariance, correlation, mode, and the safe Result-returning variants (mean_safe, std_dev_safe).


dataframe_basic.march

Tabular data pipelines with the DataFrame module. Uses an employee dataset throughout.

-- Construct from typed columns
let df = DataFrame.from_columns([
  StrCol("name",   typed_array_from_list(["Alice", "Bob", "Charlie"])),
  StrCol("dept",   typed_array_from_list(["Eng", "Eng", "Sales"])),
  IntCol("salary", typed_array_from_list([95000, 88000, 72000])),
  FloatCol("rating", typed_array_from_list([4.5, 3.9, 4.2]))
])

-- LazyFrame: filter, sort, derived column, collect
let result =
  DataFrame.lazy(df)
  |> DataFrame.filter(Gt(Col("salary"), LitInt(80000)))
  |> DataFrame.sort_by([("salary", Desc)])
  |> DataFrame.with_column("bonus", fn row ->
      match DataFrame.row_get_int(row, "salary") do
      Some(s) -> IntVal(s / 10)
      None    -> IntVal(0)
      end)
  |> DataFrame.collect()

-- GroupBy: mean salary and headcount per department
let by_dept = DataFrame.group_by(df, ["dept"])
let agg_df  = DataFrame.agg(by_dept, [Mean("salary"), Count])

-- Stats integration
let summary          = DataFrame.summarize(df)       -- per-column stats as a DataFrame
let (train, holdout) = DataFrame.train_test_split(df, 0.8)

Also shows inner_join, col_z_score normalization, and bridging a column to Stats via DataFrame.float_list.


csv_example.march

Four CSV parsing patterns from the stdlib: streaming rows, eager read, header-based field access, and TSV mode. Includes finding the oldest person in a dataset and filtering by column value.


read_file.march

Three file reading patterns: full file into a string, lines into a list, and lazy line streaming with File.with_lines and Seq.take.

-- Lazy: only reads lines as needed
File.with_lines("data.txt", fn lines ->
  let first10 = Seq.take(lines, 10) |> Seq.to_list()
  List.each(first10, println)
)

Advanced

capabilities.march

The capability security model. Shows needs IO declarations, Cap(IO.Console) and Cap(IO.Network) in function signatures, capability narrowing with cap_narrow, and higher-order capability passing.

needs IO

-- Pure functions need no capability
fn format_greeting(name : String) : String do "Hello, " ++ name ++ "!" end

-- IO functions take an explicit capability token
fn greet(cap : Cap(IO.Console), name : String) : Unit do
  println(format_greeting(name))
end

-- Narrow the root cap to least-privilege sub-capabilities
fn main() : Unit do
  let cap         = root_cap
  let console_cap = cap_narrow(cap)
  let net_cap     = cap_narrow(cap)
  greet(console_cap, "Alice")
end

templates.march

HTML templating with ~H sigils and IOList composition. Covers layout wrapping, nested partials, ETag generation, and HTML-safe interpolation.


debugger.march

Seven debugging workflows in one file: dbg(expr) logging, conditional breakpoints, :find search, :watch expressions, time-travel with :tsave/:tload, and actor message history replay.