Concurrency

March has two concurrency primitives: Tasks for async work and Actors for stateful, isolated processes.


Tasks

Task.async starts a concurrent computation. Task.await blocks until it finishes:

let t = Task.async(fn () -> expensive_computation())
let result = Task.await(t)

Run multiple tasks in parallel and collect all results:

let tasks = List.map(urls, fn url -> Task.async(fn () -> fetch(url)))
let results = Task.await_many(tasks)

Race — first to finish wins, others are cancelled:

match Task.race([
  Task.async(fn () -> primary_source()),
  Task.async(fn () -> fallback_source())
]) do
  Ok(v)  -> v
  Err(e) -> handle_error(e)
end

Actors

An actor has isolated state and processes messages one at a time. No shared memory — data races are impossible by construction:

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

  on Increment(n : Int) do
    { value: state.value + n }
  end

  on Get() do
    println(int_to_string(state.value))
    state
  end
end

Spawn an actor and send messages:

let pid = spawn(Counter)
send(pid, Increment(5))
send(pid, Increment(3))
send(pid, Get())

Fan-out and collect

A common pattern: fan out N tasks, collect when all complete. Task.async_stream maps a list to concurrent tasks and returns results as they finish:

let client = HttpClient.new_client()
let urls = ["https://api.example.com/a", "https://api.example.com/b"]

let results = Task.async_stream(urls, fn url ->
  HttpClient.get(client, url)
)

For structured coordination, use an actor as a mailbox — tasks send to it, it accumulates results:

actor Collector do
  state { items : List(String), done : Int, total : Int }
  init { items: Nil, done: 0, total: 0 }

  on Start(n : Int) do
    { state with total: n }
  end

  on Item(s : String) do
    { state with items: Cons(s, state.items), done: state.done + 1 }
  end

  on Results() do
    println(int_to_string(state.done) ++ " items collected")
    state
  end
end

Complete example: parallel computation with timeout

mod Parallel do
  pfn sum_to(n : Int) : Int do
    List.fold_left(List.range(1, n + 1), 0, fn (acc, x) -> acc + x)
  end

  fn main() do
    let inputs = [10, 20, 30, 40, 50]
    let tasks = List.map(inputs, fn n ->
      Task.async(fn () -> sum_to(n))
    )
    let results = Task.await_many_ms(tasks, 5000)
    List.each(results, fn r ->
      match r do
        Ok(v)  -> println("sum: " ++ int_to_string(v))
        Err(e) -> println("error: " ++ e)
      end
    )
  end
end
march — interactive
Click run on any snippet to try it here.
march>