Elixir Cheatsheet
Protocols and Behaviours
Use this Elixir reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
Protocols: Polymorphism by Data Type
defprotocol Size do @doc "Number of elements in the data structure." def size(data) end defimpl Size, for: BitString do def size(binary), do: byte_size(binary) end defimpl Size, for: Map do def size(map), do: map_size(map) end defimpl Size, for: Tuple do def size(tuple), do: tuple_size(tuple) end Size.size("abc") # 3 — dispatches on the value's type Size.size(%{a: 1}) # 1
A protocol dispatches to the implementation for the first argument's data type. Built-in dispatch types: Atom, BitString, Float, Function, Integer, List, Map, PID, Port, Reference, Tuple, Any, and any struct module.
Implementing for Structs
defmodule Cart do defstruct items: [] end defimpl Size, for: Cart do def size(%Cart{items: items}), do: length(items) end # fallback for everything else defprotocol Size do @fallback_to_any true def size(data) end defimpl Size, for: Any do def size(_), do: 0 end defmodule Order do @derive [Size] # use the Any implementation for this struct defstruct items: [] end
The Protocols You Meet Daily
Enum.map(my_struct, & &1) # Enumerable — implement to make a type Enum-able to_string(value) # String.Chars — powers "#{value}" interpolation inspect(value) # Inspect — controls IEx/log rendering Enum.into(pairs, %{}) # Collectable — build a container from elements Jason.encode!(struct) # Jason.Encoder — @derive {Jason.Encoder, only: [...]}
defimpl String.Chars, for: Money do def to_string(%Money{cents: c}), do: "$#{c / 100}" end defmodule User do @derive {Inspect, except: [:password_hash]} # redact fields in logs defstruct [:email, :password_hash] end
Behaviours: Contracts for Modules
defmodule Parser do @callback parse(String.t()) :: {:ok, term()} | {:error, atom()} @callback extensions() :: [String.t()] @optional_callbacks extensions: 0 end defmodule JSONParser do @behaviour Parser @impl Parser def parse(str), do: Jason.decode(str) @impl Parser def extensions, do: ["json"] end
A behaviour is a set of @callback typespecs a module promises to implement. @behaviour opts in; @impl marks each callback (the compiler warns on typos and missing callbacks). GenServer, Supervisor, Application, and Plug are all behaviours.
Protocols vs Behaviours
| Protocol | Behaviour | |
|---|---|---|
| Dispatches on | Data type of a value | Module name you pass around |
| Define with | defprotocol / defimpl | @callback + @behaviour |
| Typical use | Enumerable, Inspect, JSON encoding | GenServer, pluggable adapters |
# behaviour-style plug-in module, configured then called by name parser = Application.fetch_env!(:my_app, :parser) # e.g. JSONParser parser.parse(input)
Swappable Implementations in Config
# config/config.exs config :my_app, :mailer, MyApp.Mailer.SMTP # config/test.exs config :my_app, :mailer, MyApp.Mailer.Fake defp mailer, do: Application.fetch_env!(:my_app, :mailer) def notify(user), do: mailer().send(user.email, "Welcome")
Behaviour + config-selected module is the standard pattern for test fakes and adapters (the approach libraries like Mox formalize: Mox.defmock(MailerMock, for: MyApp.Mailer)).