Let Erlang Crash

A fun, irreverent guide to the world's most indestructible programming language

View on GitHub

Chapter 26: Elixir: The Cool Younger Sibling

In 2011, Jose Valim — a Ruby core team member — discovered the BEAM and thought: “This virtual machine is incredible. The language on top of it could use some work.” So he created Elixir: all the power of the BEAM with a syntax that doesn’t scare off Ruby and Python developers. The result is one of the most successful language-on-VM stories since Kotlin on the JVM.


What Is Elixir?

Elixir is a programming language that:

It’s not a replacement for Erlang. It’s an alternative syntax with extra features, sharing the same runtime.

Erlang vs. Elixir: Side by Side

A GenServer

Erlang:

-module(counter).
-behaviour(gen_server).
-export([start_link/0, increment/0, get/0]).
-export([init/1, handle_call/3, handle_cast/2]).

start_link() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, 0, []).

increment() -> gen_server:cast(?MODULE, :increment).
get() -> gen_server:call(?MODULE, :get).

init(Initial) -> {ok, Initial}.

handle_cast(:increment, Count) -> {noreply, Count + 1}.

handle_call(:get, _From, Count) -> {reply, Count, Count}.

Elixir:

defmodule Counter do
  use GenServer

  def start_link(initial \\ 0) do
    GenServer.start_link(__MODULE__, initial, name: __MODULE__)
  end

  def increment, do: GenServer.cast(__MODULE__, :increment)
  def get, do: GenServer.call(__MODULE__, :get)

  @impl true
  def init(initial), do: {:ok, initial}

  @impl true
  def handle_cast(:increment, count), do: {:noreply, count + 1}

  @impl true
  def handle_call(:get, _from, count), do: {:reply, count, count}
end

The structure is identical. The callbacks are the same. The return values are the same. It’s the same OTP GenServer — just with different syntax.

Pattern Matching

Erlang:

area({circle, R}) -> math:pi() * R * R;
area({rect, W, H}) -> W * H.

Elixir:

def area({:circle, r}), do: :math.pi() * r * r
def area({:rect, w, h}), do: w * h

Pipe Operator

This is Elixir’s most beloved feature. Instead of nested function calls:

%% Erlang: read inside-out
string:uppercase(string:trim(binary_to_list(Data))).
# Elixir: read left-to-right
Data
|> :binary.bin_to_list()
|> String.trim()
|> String.upcase()

The pipe operator |> passes the result of each expression as the first argument to the next function. It’s purely syntactic sugar but dramatically improves readability for data transformation pipelines.

What Elixir Adds

Metaprogramming (Macros)

Elixir has a powerful macro system built on Erlang’s AST:

defmacro unless(condition, do: block) do
  quote do
    if !unquote(condition), do: unquote(block)
  end
end

Erlang has parse transforms and macros (-define), but Elixir’s macro system is more accessible and more commonly used.

Protocols (Ad-hoc Polymorphism)

defprotocol Stringify do
  def to_string(data)
end

defimpl Stringify, for: Integer do
  def to_string(n), do: Integer.to_string(n)
end

defimpl Stringify, for: List do
  def to_string(list), do: Enum.join(list, ", ")
end

Similar to Erlang behaviours but dispatched on data type at runtime.

Sigils and String Interpolation

name = "World"
"Hello, #{name}!"       # String interpolation
~r/hello\s+world/       # Regex sigil
~w(foo bar baz)          # Word list sigil

Erlang uses io_lib:format for string formatting. Elixir’s interpolation is more ergonomic.

Mix: The Build Tool

Mix is Elixir’s build tool, inspired by Bundler/Leiningen/Cargo:

mix new my_app       # Create a project
mix deps.get         # Fetch dependencies
mix compile          # Compile
mix test             # Run tests
mix format           # Auto-format code

Erlang has rebar3, which serves a similar role.

The Ecosystem

Phoenix Framework

Phoenix is to Elixir what Rails is to Ruby — a full-featured web framework. But it runs on the BEAM, which means:

Hex Package Manager

Hex is the package manager for both Elixir and Erlang. Yes, Erlang packages and Elixir packages live on the same registry and can depend on each other.

# Elixir packages can depend on Erlang packages
{:cowboy, "~> 2.9"}    # Erlang HTTP server, used from Elixir

# Erlang rebar3 projects can depend on Hex packages too
{deps, [{jsx, "3.1.0"}]}.

Nerves

Nerves is an Elixir framework for embedded systems — running BEAM on IoT devices. Your Raspberry Pi can run Elixir with full OTP supervision.

Nx and Livebook

Nx is numerical computing for Elixir, with GPU support. Livebook is an interactive notebook (like Jupyter) for Elixir. The BEAM ecosystem is expanding into data science.

Calling Between Languages

Elixir and Erlang interoperate perfectly:

# Calling Erlang from Elixir
:crypto.hash(:md5, "hello")
:ets.new(:my_table, [:set, :public])
:gen_server.call(pid, :request)
%% Calling Elixir from Erlang
'Elixir.MyModule':my_function(Args).
'Elixir.Phoenix.PubSub':broadcast(PubSub, Topic, Message).

Elixir modules are atoms with the Elixir. prefix. The interop is zero-cost — they share the same bytecode, the same processes, the same messages.

Should You Learn Elixir Instead?

Learn Erlang if:

Learn Elixir if:

Ideally, learn both. Understanding Erlang makes you a better Elixir developer, and vice versa. They’re the same platform with different accents.

What About Gleam?

Gleam is the newest BEAM language — statically typed with Rust-inspired syntax:

pub fn fibonacci(n: Int) -> Int {
  case n {
    0 -> 0
    1 -> 1
    n -> fibonacci(n - 1) + fibonacci(n - 2)
  }
}

Gleam compiles to both BEAM bytecode and JavaScript. It’s young but growing fast, especially among developers who want static types on the BEAM.

Key Takeaways

The BEAM ecosystem is richer than ever. Whether you write Erlang, Elixir, or Gleam, you get the same legendary runtime — processes, supervisors, distribution, hot code reloading, and decades of battle-tested engineering. Choose the syntax that makes you happy, and let the BEAM handle the hard parts.


← Previous: Real-World Erlang Next: Erlang Never Died →