F0 · onramp
Elixir for C# developers
If you write C# — ASP.NET controllers, EF Core queries, Task and
async/await — you already hold most of the ideas Elixir is built from. What is
genuinely different is the machine underneath. This page maps the gap: the two runtimes, the functional
features C# has steadily borrowed, and a library that makes the translation almost line for line.
C# has spent twenty years drifting toward functional programming — generics, closures, LINQ, records, pattern matching. Elixir began there, on a runtime designed for a different goal. Knowing where the two meet, and where they part, is the fastest way in.
Two runtimes: the CLR and the BEAM
Both are managed, garbage-collected virtual machines that run cross-platform — but they were built to win at different things. The CLR targets raw throughput and a vast, statically typed class library: one process, many threads, shared memory. The BEAM — the Erlang VM Elixir runs on — targets concurrency and uptime: a great many isolated processes that share nothing and communicate by message. Choose a dimension to see how each approaches it.
CLR vs BEAM · select a dimension
The headline difference is concurrency and failure. On the CLR you protect shared state and catch exceptions; on the BEAM you isolate state in processes and let the broken ones crash and restart. The rest of this course is, in large part, learning to think that way.
What C# already gave you
Most of C#'s recent features are borrowed from functional languages, so a lot of Elixir will feel
familiar. LINQ is map, filter, and reduce by another name. Records are
immutable data. Switch expressions are pattern matching with guards.
Tuples and deconstruction are destructuring. Nullable reference types are an apology for
null — the very problem Elixir avoids by matching. Modern C# already reads
halfway to Elixir:
// modern C#: a record, LINQ, a switch expression record Lesson(string Id, bool Completed); var doneIds = lessons .Where(l => l.Completed) .Select(l => l.Id) .ToList(); var label = percent switch { 0 => "not started", var p when p >= 100 => "complete", _ => "in progress" };
The same three ideas in Elixir — a struct, a pipeline of Enum calls, and a case:
defmodule Lesson do defstruct [:id, :completed] end done_ids = lessons |> Enum.filter(& &1.completed) |> Enum.map(& &1.id) label = case percent do 0 -> "not started" p when p >= 100 -> "complete" _ -> "in progress" end
If that second block reads naturally, you are most of the way there. The syntax differs —
|> for the chain, case for the switch — but the shape of
the thinking is the one C# has been moving toward for years.
language-ext: the bridge
language-ext is a pure functional-programming
framework for C#, by Paul Louth. It adds the types functional languages take for granted —
Option and Either in place of null
and exceptions, persistent immutable collections, validation, and an IO monad for
side effects — and even an actor library, echo-process, modelled on Erlang. Used well, it
turns C# into something that reads remarkably close to Elixir. Select a concept to see both side by side.
language-ext ↔ Elixir · select a concept
The point is not that C# can imitate Elixir, but that the ideas are the same ones. If you
already reach for Option and Either, Elixir's tagged tuples
and matching will feel like home — on a runtime that was built for them.
A concept map
A rough phrasebook from the C# you know — plus language-ext where it helps — to its Elixir counterpart.
null→Option<T>- a value or
nil, always matched — Elixir has nonullto forget. - exceptions →
Either/Try {:ok, value}or{:error, reason}— failure is a value you match.IEnumerable+ LINQEnumandStream, composed with the pipe|>.record- a
struct— immutable by default, “updated” by building a new one. - switch expression
caseand function-head matching, withwhenguards.Dictionary→Map%{}maps, persistent and matched by key.Task/async- processes and messages — or
Taskfor a simple parallel job. - class with methods
- a
defmoduleof functions; no instances, only data in and out. - interface / DI container
- behaviours and protocols — contracts and polymorphism by data type.
- actor (echo-process)
- a process, or a
GenServer—State -> Message -> State.
C# · the CLR
A language travelling toward functional programming, on a runtime built for throughput and shared memory.
Elixir · the BEAM
A language that began functional, on a runtime built for concurrency and uptime.
That is the whole gap. Begin the course at F1 —
Algebra, or read the runtime in depth in F0.2 — the
BEAM & OTP. Pattern matching — the idea under Option and
Either — gets its own module in F3.02.