Elixir Random Notes
An important feature of Elixir is that any two types can be compared; this is particularly useful in sorting. We don’t need to memorize the sort order, but it is important to be aware of it:
number < atom < reference < function < port < pid < tuple < map < list < bitstring
Elixir implements list collections as linked lists. This means that accessing the list length is an operation that will run in linear time (O(n)). For this reason, it is typically faster to prepend than to append.
hd [3.14, :pie, "Apple"]
tl [3.14, :pie, "Apple"]
[head | tail] = [3.14, :pie, "Apple"]
Tuples are similar to lists, but are stored contiguously in memory. This makes accessing their length fast but modification expensive; the new tuple must be copied entirely to memory.
iex> {3.14, :pie, "Apple"}
iex> File.read("path/to/existing/file")
{:ok, "... contents ..."}
iex> File.read("path/to/unknown/file")
{:error, :enoent}
iex> [foo: "bar", hello: "world"]
[foo: "bar", hello: "world"]
iex> [{:foo, "bar"}, {:hello, "world"}]
[foo: "bar", hello: "world"]
The three characteristics of keyword lists highlight their importance:
For these reasons, keyword lists are most commonly used to pass options to functions.
In Elixir, maps are the “go-to” key-value store. Unlike keyword lists, they allow keys of any type and are un-ordered.
iex> map = %{:foo => "bar", "hello" => :world}
iex> map[:foo]
iex> %{:foo => "bar", :foo => "hello world"}
%{foo: "hello world"}
iex> %{foo: "bar", hello: "world"} == %{:foo => "bar", :hello => "world"}
true
there is a special syntax for maps containing only atom keys:
iex> %{foo: "bar", hello: "world"} == %{:foo => "bar", :hello => "world"}
In addition, there is a special syntax for accessing atom keys:
iex> map.hello
"world"
Another interesting property of maps is that they provide their own syntax for updates:
iex> %{map | foo: "baz"}
%{foo: "baz", hello: "world"}
For a full list of functions visit the official Enum docs; for lazy enumeration use the Stream module.
The match operator performs assignment when the left side of the match includes a variable. In some cases this variable rebinding behavior is undesirable. For these situations we have the pin operator: ^. When we pin a variable we match on the existing value rather than rebinding to a new one.
iex> x = 1
1
iex> ^x = 2
** (MatchError) no match of right hand side value: 2
iex> {x, ^x} = {2, 1}
{2, 1}
iex> x
2
Elixir 1.2 introduced support for pins in map keys and function clauses.
def deps do
[
{:phoenix, "~> 1.1 or ~> 1.2"},
{:phoenix_html, "~> 2.3"},
{:cowboy, "~> 1.0", only: [:dev, :test]},
{:slime, "~> 0.14"}
]
end
$ mix deps.get
The current environment can be accessed using Mix.env. As expected, the environment can be changed via the MIX_ENVenvironment variable: $ MIX_ENV=prod mix compile
Always document a module. If you do not intend to document a module, do not leave it blank. Consider annotating the module false:
@moduledoc false
assert
assert_raise
assert_received
does not wait for messages, with assert_receive you can specify a timeout.capture_io
capture_log
refute
when you want to ensure a statement is always false.setup
setup_all
defmodule ExampleTest do
use ExUnit.Case
doctest Example
setup_all do
{:ok, recipient: :world}
end
test "greets", state do
assert Example.hello() == state[:recipient]
end
end
The simple answer to mocking in Elixir is: don’t. You may instinctively reach for mocks but they are highly discouraged in the Elixir community and for good reason.
For a longer discussion there is this excellent article. The gist is, that instead of mocking away dependencies for testing (mock as a verb), it has many advantages to explicitly define interfaces (behaviors) for code outside your application and using Mock (as a noun) implementations in your client code for testing.
To switch the implementations in your application code, the preferred way is to pass the module as arguments and use a default value. If that does not work, use the built-in configuration mechanism. For creating these mock implementations, you don’t need a special mocking library, only behaviours and callbacks.