jonnifyelixir
F0 · History / Elixir for C# developers

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

CONCURRENCY .NET CLR threads + async/await the BEAM millions of processes

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

C# · language-ext

      
Elixir

      

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 no null to forget.
exceptions → Either / Try
{:ok, value} or {:error, reason} — failure is a value you match.
IEnumerable + LINQ
Enum and Stream, composed with the pipe |>.
record
a struct — immutable by default, “updated” by building a new one.
switch expression
case and function-head matching, with when guards.
Dictionary → Map
%{} maps, persistent and matched by key.
Task / async
processes and messages — or Task for a simple parallel job.
class with methods
a defmodule of 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.