Concurrency
This is the BEAM topic. The virtual machine's superpower is cheap, isolated processes that share nothing and talk only by sending immutable messages - millions of them, each with its own heap and mailbox. The task is the canonical one: spawn 5 workers that each square a number 1..5, have each send its result back, and let the parent receive and sum them to 55. Watch Erlang, Elixir, and LFE use the raw spawn/!/receive trio, Gleam wrap the very same primitives in typed actors so messages are checked at compile time, and Luerl stand apart - Lua has no BEAM processes of its own, so true concurrency has to come from the Erlang host.
-module(squares).
-export([run/0]).
run() ->
Self = self(),
%% spawn one process per number; each sends {N, N*N} back to us.
lists:foreach(
fun(N) ->
spawn(fun() -> Self ! {N, N * N} end)
end,
lists:seq(1, 5)),
collect(5, 0).
%% receive exactly Count messages, summing the squares.
collect(0, Sum) -> Sum;
collect(Count, Sum) ->
receive
{_N, Square} -> collect(Count - 1, Sum + Square)
end.
%% squares:run() =:= 55Erlang's three primitives do everything: spawn/1 starts a process, Pid ! Msg sends asynchronously, and receive blocks the parent until a matching message arrives - here we loop receive five times to drain the mailbox and accumulate the sum.
defmodule Squares do
def run do
parent = self()
# spawn one process per number; each sends {n, n*n} back.
1..5
|> Enum.each(fn n ->
spawn(fn -> send(parent, {n, n * n}) end)
end)
# collect 5 replies and sum the squares.
Enum.reduce(1..5, 0, fn _i, sum ->
receive do
{_n, square} -> sum + square
end
end)
end
end
# Squares.run() == 55
# Real code often reaches for Task.async_stream/2 instead of raw spawn/send.Elixir wraps the same spawn/send/receive trio in friendlier names and pipes; Enum.reduce/3 over 1..5 drives the parent's receive loop, though everyday code usually prefers higher-level Tasks.
import gleam/erlang/process.{type Subject}
import gleam/int
import gleam/io
import gleam/list
pub fn run() -> Int {
// A Subject is a typed channel: it only ever carries Int here.
let inbox: Subject(Int) = process.new_subject()
// spawn one process per number; each sends back its square.
list.each([1, 2, 3, 4, 5], fn(n) {
process.spawn(fn() { process.send(inbox, n * n) })
})
// receive 5 typed messages and fold them into a sum.
list.fold([1, 2, 3, 4, 5], 0, fn(sum, _i) {
case process.receive(inbox, within: 1000) {
Ok(square) -> sum + square
Error(_) -> sum
}
})
}
pub fn main() {
io.println(int.to_string(run()))
// prints "55"
}Gleam keeps the BEAM model but makes it sound: a Subject(Int) is a statically typed mailbox, so send and receive can only carry Int, and receive returns a Result (with a timeout) instead of blocking forever - no untyped messages, no surprises.
(defmodule squares
(export (run 0)))
(defun run ()
(let ((self (self)))
;; spawn one process per number; each sends (tuple n (* n n)) back.
(lists:foreach
(lambda (n)
(spawn (lambda () (! self (tuple n (* n n))))))
(lists:seq 1 5))
(collect 5 0)))
;; receive Count messages, summing the squares.
(defun collect
((0 sum) sum)
((count sum)
(receive
((tuple _n square) (collect (- count 1) (+ sum square))))))
;; (squares:run) => 55LFE expresses the identical primitives as S-expressions: spawn takes a lambda, (! pid msg) is the send operator, and receive pattern-matches the incoming tuple - the two-clause collect recurses until the mailbox is drained.
-- Luerl runs Lua *on* the BEAM, but plain Lua has no processes,
-- no spawn, no mailbox, and no message passing of its own.
-- So "concurrency" here is just a sequential map over the numbers:
local function squares()
local sum = 0
for n = 1, 5 do
sum = sum + n * n -- the work a BEAM worker would do, inline
end
return sum
end
print(squares()) --> 55
-- To get *real* concurrency you must cross back into the host:
-- the embedding Erlang/Elixir app spawns the processes and runs
-- a small Lua chunk inside each one via luerl:do/2, then collects
-- the results on the BEAM side. The parallelism lives in Erlang,
-- not in the Lua code.Lua has no native BEAM processes, so the language itself can only compute the sum sequentially; genuine concurrency comes from the host - the Erlang/Elixir program that embeds Luerl spawns the processes and runs each Lua chunk inside one, doing the spawning and collecting on the BEAM side.