4 Functions and Modules
4.2 Struct
defmodule Candidate do
defstruct [name: "qiushi", age: 18]
end
= %Candidate{name: "tom"}
c #> %Candidate{name: "tom", age: 18}
4.4 Module attributes
Module attributes are computed at compile time.
defmodule Mascot do
@mascots %{
democratic: "monkey",
republican: "elephant",
green: "plant",
}
@parties Map.keys(@mascots)
def mascot(party) do
@mascots[party]
end
def logo(party, size) when party in @parties do
= mascot(party)
m
do_logo_size(m, size)end
def logo(party, size) do
:error, :unknown_party}
{end
defp do_logo_size(mascot, :small), do: "#{mascot}_small.png"
defp do_logo_size(mascot, :normal), do: "#{mascot}_normal.png"
defp do_logo_size(mascot, :large), do: "#{mascot}_large.png"
defp do_logo_size(mascot, unknown), do: {:error, :unknown_size}
end
4.6 Use, Import and Alias
4.6.1 Use
The use
macro is frequently used as an extension point. This means that, when you use a module FooBar, you allow that module to inject any code in the current module, such as importing itself or other modules, defining new functions, setting a module state, etc.
For example, in order to write tests using the ExUnit framework, a developer should use the ExUnit.Case module:
defmodule AssertionTest do
use ExUnit.Case, async: true
"always pass" do
test true
assert end
end
Behind the scenes, use requires the given module and then calls the __using__/1
callback on it allowing the module to inject some code into the current context. Some modules (for example, the above ExUnit.Case
, but also Supervisor
and GenServer
) use this mechanism to populate your module with some basic behaviour, which your module is intended to override or complete.
Generally speaking, the following module:
defmodule Example do
use Feature, option: :value
end
is compiled into
defmodule Example do
require Feature
Feature.__using__(option: :value)
end
Since use allows any code to run, we can’t really know the side-effects of using a module without reading its documentation. Therefore use this function with care and only if strictly required. Don’t use use
where an import or alias would do.
4.6.2 Import
If we want to import functions rather than aliasing the module we can use import
:
> import List
iex#> nil
> last([1, 2, 3])
iex#> 3
By default all functions and macros are imported but we can filter them using the :only
and :except
options.
To import specific functions and macros, we must provide the name/arity pairs to :only
and :except
. Let’s start by importing only the last/1
function:
> import List, only: [last: 1]
iex> first([1, 2, 3])
iex#> ** (CompileError) iex:13: undefined function first/1
> last([1, 2, 3])
iex#> 3
If we import everything except last/1
and try the same functions as before:
> import List, except: [last: 1]
iex> first([1, 2, 3])
iex#> 1
> last([1, 2, 3])
iex#> ** (CompileError) iex:3: undefined function last/1
In addition to the name/arity pairs there are two special atoms, :functions and :macros, which import only functions and macros respectively:
import List, only: :functions import List, only: :macros
4.6.3 Alias
Allows us to alias module names; used quite frequently in Elixir code:
defmodule Sayings.Greetings do
def basic(name), do: "Hi, #{name}"
end
defmodule Example do
alias Sayings.Greetings
def greeting(name), do: Greetings.basic(name)
end
Without alias
defmodule Example do
def greeting(name), do: Sayings.Greetings.basic(name)
end
If there’s a conflict between two aliases or we just wish to alias to a different name entirely, we can use the :as
option:
defmodule Example do
alias Sayings.Greetings, as: Hi
def print_message(name), do: Hi.basic(name)
end
It’s even possible to alias multiple modules at once:
defmodule Example do
alias Sayings.{Greetings, Farewells}
end