Module C3.01
Two functional runtimes — the BEAM & the CLR
You already trust a runtime. F# asks you to trust a different one — and to know where the two part.
Elixir runs on the BEAM; F# runs on the .NET CLR. Both host
functional code with immutable data and first-class functions, so most of the language transfers. The
runtimes do not. The BEAM is a process-oriented virtual machine: cheap isolated processes, a heap and a
garbage collector per process, preemptive reduction-based scheduling, and the OTP libraries with
supervision and "let it crash". The CLR is a managed runtime for many languages: OS threads, a shared
generational garbage collector, the thread pool, and async /
Task for concurrency. F#'s actor is the MailboxProcessor —
a mailbox and a recursive receive loop — but there is no built-in supervision tree. This module names the
differences on four axes across three dives.
The same code, a different machine under it
A runtime fixes four things that shape how a program behaves under load and under failure: what the unit of concurrency is, how that unit is scheduled, how memory is reclaimed, and what happens when something faults. The BEAM and the CLR answer all four differently. Neither answer is the language's — both Elixir-on-the-BEAM and F#-on-the-CLR are functional, immutable, and pattern-matched. The difference is the host.
The first dive states the BEAM model an Elixir developer already lives inside, in the runtime's own terms. The second states the CLR model: managed execution, the JIT, a shared generational GC, threads and the thread pool, and the type system the CLR enforces. The third reads the two side by side — what carries over without change, what gains a static type, and the one thing the CLR does not hand you: OTP-style supervision.
The three dives · select one
The runtime an Elixir developer already lives inside: lightweight isolated processes, a per-process heap and per-process GC, preemptive reduction-based scheduling, and OTP supervision with "let it crash".
Open this dive · or pick another above
The language carries over almost whole. The runtime does not — and the runtime is what answers the four questions that decide how a system behaves when it is busy or breaking.
Four axes, two answers
Select an axis. The diagram marks which axis is in focus; the readout names the BEAM's answer and the CLR's answer for that axis, and the F# construct that meets it. Every value below is a documented fact about the two runtimes, not a benchmark — the point is the shape of each model, not a number.
BEAM vs CLR · select an axis
MailboxProcessor over async| axis | BEAM | CLR |
|---|---|---|
| concurrency unit | a lightweight isolated process | an OS thread, usually via the thread pool |
| scheduling | preemptive, reduction-counted, per scheduler | OS-scheduled threads; cooperative async over the pool |
| garbage collection | a heap and collector per process | one shared generational collector |
| fault model | isolation + OTP supervision, "let it crash" | exceptions; no built-in supervision tree |
Four axes, two answers each. The dives that follow take them apart — first the BEAM's, then the CLR's, then the carry-over.
The stateful server, both ways
The clearest single contrast is the stateful server. On the BEAM you reach for a GenServer
(or a hand-written receive loop): a process that holds state and answers messages.
In F# the closest construct is the MailboxProcessor — an agent with a mailbox and a
recursive async receive loop. The shape is the same; the difference is that the F#
agent is one object on the shared runtime, not a supervised BEAM process.
Elixir — a GenServer holding a counter
# Elixir — GenServer def handle_call(:total, _from, total), do: {:reply, total, total} def handle_cast({:incr, n}, total), do: {:noreply, total + n} GenServer.cast(pid, {:incr, 5}) GenServer.call(pid, :total) # => 5
F# — a MailboxProcessor holding a counter (verified in FSI)
type Msg = Incr of int | Total of AsyncReplyChannel<int> let counter = MailboxProcessor.Start(fun inbox -> let rec loop total = async { let! msg = inbox.Receive() match msg with | Incr n -> return! loop (total + n) | Total reply -> reply.Reply total; return! loop total } loop 0) counter.Post(Incr 5) counter.PostAndReply(fun ch -> Total ch) // val it : int = 5
Elixir / BEAM
A GenServer is a supervised process with a mailbox; a crash is isolated and the supervisor restarts it.
F# / CLR
A MailboxProcessor is an agent with a mailbox on the shared runtime; the receive loop is the same shape, but supervision is not built in.
The three dives
Read them in order. Each stands on valid F# beside real Elixir: every // val it :
echo below was produced by dotnet fsi, and the Elixir is the idiom an Elixir
developer would write. Together they take a BEAM developer from trusting the runtime by habit to knowing
which of those assumptions still hold on the CLR, and which one — supervision — has to be rebuilt.
The BEAM you know
The runtime in its own terms: isolated processes, per-process GC, preemptive scheduling, OTP supervision.
- the BEAMthe baseline an Elixir developer already lives inside
The CLR — managed execution & the type system
JIT, a shared generational GC, threads and the thread pool, async / Task, and the type system the CLR enforces.
- the CLRmanaged execution, contrasted with the BEAM
What carries over, what changes
Immutability and the pipe carry over; static types and exhaustiveness are gained; OTP supervision is the thing given up.
- the seamkept, gained, and given up — across the two runtimes
References
Sources
- Microsoft — .NET documentation — the CLR, managed execution, garbage collection, and the threading model.
- Microsoft — F# documentation — async workflows and the
MailboxProcessor. - FSharp.Core API reference —
MailboxProcessorand theAsyncmodule. - The F# Software Foundation — F#'s home on the .NET runtime.
Related in this course
- C3 — F# for Elixir developers — the chapter this module opens.
- C0.2 — The .NET runtime & the open-source turn — the CLR's own history, in depth.
- Elixir — The BEAM's evolution — the runtime F# is measured against here.
- Elixir — Processes & the actor model — the BEAM concurrency unit, in its own course.