SCUA

How-to

Run many things at once

Sometimes you have a pile of independent work — fetch twenty URLs, read a hundred files, hit a dozen APIs — and doing them one at a time is pure waiting. SCUA has two built-ins for this fan-out: wait_all and map_all. No import, no async/await coloring, no future-wrangling — you hand them a list of things to do and they hand back the results.

#wait_all: run a list, wait for all of it

wait_all takes a list of zero-argument functions and returns their outcomes in order:

let results = wait_all([
  fn() return 1 + 1 end,
  fn() return "two" end,
  fn() return [3, 3, 3] end,
])
print(`first arm: {results[0]}`)    -- 2
print(`second arm: {results[1]}`)   -- two

Two properties matter:

  • Ordered. results[0] is the first arm's outcome, results[1] the second — the result list lines up with the arm list, regardless of which finished first.
  • All-settled. One arm failing never cancels the others. You always get a slot for every arm; there's no "first error wins" that throws the rest away.

#The result is the arm's value, as-is

Each slot holds whatever that arm produced, unchanged:

  • A plain arm gives a plain value — read it directly.
  • An arm that does I/O (like http.get or fs.read) gives back that call's Ok/Error — match it as you always would.
  • An arm that faults (a bug — an index out of range, a nil field) gives an Error carrying a tagged fault record.

So you match Ok/Error when your arms return it, and read the value directly when they don't. There's no extra wrapping layer to peel off.

#Telling a bug apart from a failure

Because a faulting arm lands as an Error, you can catch a bug in one arm without it taking down the batch. The fault Error carries a fault tag (and a message) so you can tell a genuine bug apart from an ordinary failure before you decide what to do:

let mixed = wait_all([
  fn() return "ok" end,
  fn() return error("boom") end,
])
match mixed[1]
  Ok(v) -> print(`unexpected ok: {v}`)
  Error(e) -> print(`arm 1 failed: fault={e.fault} ({e.message})`)
end

#map_all: the same thing over a list

When every arm does the same thing to a different input, map_all is the natural spelling. It maps a function over a list, running at most concurrency at a time (default 8), waits for all, and aligns the results with the input:

let doubled = map_all([10, 20, 30, 40], fn(x) return x * 2 end, { concurrency = 4 })
print(`doubled: {doubled[0]}, {doubled[1]}, {doubled[2]}, {doubled[3]}`)

Same semantics as wait_all: ordered, all-settled, and each slot is that call's value as-is. Iterate the results like any list:

let lengths = map_all(["a", "bb", "ccc"], fn(s) return s.len() end)
for i in 0:lengths.len() do
  print(`{i}: length {lengths[i]}`)
end

The concurrency cap is the throttle: { concurrency = 8 } keeps at most eight requests in flight, so you can fan out over a thousand URLs without opening a thousand sockets at once.

#Two gotchas

Don't put ? on it. wait_all and map_all never fail "as a whole" — there is no outer Ok/Error to short-circuit. A per-arm failure lives in that arm's slot, and the caller always waits for every arm. wait_all([...])? is a mistake.

Each arm must be a function — don't call it eagerly. wait_all runs the functions you give it; it can't run a value you already computed. Writing wait_all([http.get(u)]) calls http.get first, before wait_all ever sees it, so nothing overlaps — SCUA catches this and raises a loud error naming the arm. Wrap each call in fn() ... end:

wait_all([ fn() return http.get(a) end, fn() return http.get(b) end ])   -- right
wait_all([ http.get(a), http.get(b) ])                                   -- wrong: calls run eagerly

#Making the waits actually overlap

By default SCUA's I/O is blocking, so on the default platform the arms run one after another — you still get the same ordered, all-settled results, but no wall-clock saving. Add --io=async and the picture changes: fs/http calls run on an offload pool, so under wait_all/map_all the waits genuinely overlap.

scua --io=async --allow-net=api.example.com fetch_all.scua
-- with --io=async, all of these are in flight at once and waited together
let pages = map_all(urls, fn(u) return http.get(u) end, { concurrency = 8 })

--io=async is an optimization flag, not a requirement: the results are identical either way — ordered, all-settled — only the wall-clock differs. Write the code once; add the flag when overlap pays off.

See the runnable examples/wait_all.scua for all of the above in one file.