Elixir by Example

Modules and Named Functions

Edit any cell and run it in your browser Download source

Named functions live in modules. def makes a public function, defp a private one.

defmodule Temperature do
  @moduledoc "Converts between Celsius and Fahrenheit."

  @doc "Celsius to Fahrenheit."
  @spec to_f(number()) :: float()
  def to_f(c), do: c * 9 / 5 + 32

  @spec to_c(number()) :: float()
  def to_c(f), do: (f - 32) * 5 / 9

  # Multi-line bodies use do/end.
  def describe(c) do
    "#{c}°C is #{format(to_f(c))}°F"
  end

  defp format(value), do: :erlang.float_to_binary(value, decimals: 1)
end

IO.puts(Temperature.describe(100))
IO.inspect(Temperature.to_c(212))
100°C is 212.0°F
100.0

Private functions can't be called from outside.

try do
  apply(Temperature, :format, [1.0])
rescue
  e in UndefinedFunctionError -> IO.puts(Exception.message(e))
end
function Temperature.format/1 is undefined or private

Functions are identified by name and arity. Default arguments (\\) generate the smaller arities for you.

defmodule Greeter do
  def hello(name, punctuation \\ "!") do
    "Hello, #{name}#{punctuation}"
  end

  # Multiple clauses, matched top to bottom.
  def kind(0), do: :zero
  def kind(n) when n > 0, do: :positive
  def kind(_), do: :negative
end

IO.puts(Greeter.hello("Ada"))
IO.puts(Greeter.hello("Ada", "?"))
IO.inspect(Enum.map([0, 5, -5], &Greeter.kind/1))
Hello, Ada!
Hello, Ada?
[:zero, :positive, :negative]

Module attributes like @limit are constants, computed at compile time.

defmodule Limits do
  @limit 3
  def limit, do: @limit
end

IO.inspect(Limits.limit())
3

alias shortens a module name. import lets you call functions without the prefix; prefer only: so readers can see where a function comes from.

alias Temperature, as: T
IO.inspect(T.to_f(0))

import Integer, only: [digits: 1]
IO.inspect(digits(2026))
32.0
[2, 0, 2, 6]

Pitfall: @spec is documentation and input for tools like Dialyzer, not a runtime check. Elixir 1.20's type checker infers types from your code and guards, but a spec saying integer() won't stop someone passing a string.

Pitfall: in a script, you can't import (or use/require) a module defined earlier in the same file at top level. It fails to compile with "module Greeter is not loaded but was defined". Move the module to its own file, or call it with its full name.