5 Elixir tricks you should know

By: Daniel Xu

alias __MODULE__

This one looks mysterious at first, but once we break it down, it’s very straightforward.

alias allows you to define aliases for the module name, for example:

alias Foo.Bar will set up an alias for module Foo.Bar, and you can reference that module with just Bar.

__MODULE__ is a compilation environment macros which is the current module name as an atom.

Now you know alias __MODULE__ just defines an alias for our Elixir module. This is very useful when used with defstruct which we will talk about next.

In the following example, we pass API.User struct around to run some checks on our data. Instead of writing the full module name, we set up an alias User for it and pass that around. It’s pretty concise and easy to read.

defmodule API.User do
  alias __MODULE__

  defstruct name: nil, age: 0

  def old?(%User{name: name, age: age} = user) do
    ...
  end
end

In case of module name changing, you can also do this:

alias __MODULE__, as: SomeOtherName

defstruct with @enforce_keys

Whenever you want to model your data with maps, you should also consider struct because struct is a tagged map which offers compile time checks on the key and allows us to do run-time checks on the struct’s type, for example:

you can’t create a struct with field that is not defined. In the following example you can also see how we apply the first trick we just learned.

defmodule Fun.Game do
  alias __MODULE__
  defstruct(
    time: nil,
    status: :init
  )

  def new() do
    %Game{step: 1}
  end
end

iex> IO.inspect Fun.Game.new()
iex> ** (KeyError) key :step not found in: %{__struct__: Fun.Game, status: :init, time: nil}

However, sometimes you want to ensure that some fields are present whenever you create a new struct. Fortunately, Elixir provides @enforce_keys module attribute for that:

defmodule Fun.Game do
  @enforce_keys [:status]

  alias __MODULE__
  defstruct(
    time: nil,
    status: :init
  )

  def new() do
    %Game{}
  end
end

iex> Fun.Game.new()
iex> ** (ArgumentError) the following keys must also be given when building struct Fun.Game: [:status]

Based on the result, you can see that in this case we can’t rely on the default value of status, we need to specify its value when we create a new Game:

def new(status) do
  %Game{status: status}
end

iex> Fun.Game.new(:won)
iex> %Fun.Game{status: :won, time: nil}

v() function in iex

Whenever I write a GenServer module, I usually want to start the server and check the result in iex. One thing that really bothers me is that I almost always forget to pattern match the process pid, like this:

iex(1)> Metex.Worker.start_link()
{:ok, #PID<0.472.0>}

then, I need to type that command again with pattern matching:

{:ok, pid} = Metex.Worker.start_link()

Being tired of doing this over and over again, I found that you can use v() to return the result from last command:

iex(1)> Metex.Worker.start_link()
{:ok, #PID<0.472.0>}
iex(2)> {:ok, pid} = v()
{:ok, #PID<0.472.0>}
iex(3)>  pid
#PID<0.472.0>

This trick saves me couple of seconds every time I use it, I hope that you will find it helpful too.

cancel bad command in iex

Have you ever had this kind of moment when you use iex:

iex(1)> a = 1 + 1'
...(2)>
...(2)>
...(2)>
BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded
       (v)ersion (k)ill (D)b-tables (d)istribution

Normally, I will ctrl + c twice to exit iex and create a new one. However, sometimes you’ve already typed in a bunch of commands, and you definitely want to keep the session. Here is what you can do: #iex:break

iex(2)> a = 1 + 1
iex(2)> b = 1 + 1'
...(2)>
...(2)> #iex:break
** (TokenMissingError) iex:1: incomplete expression

iex(2)> a
2

From the code block above, you can see that we still have the session after canceling a bad command.

bind value to an optional variable

I’m sure most of people know that you can bind a value to an optional variable like this:

_dont_care = 1

The reason why I bring this up is because we can actually apply this trick to our functions to make them more readable:

defp accept_move(game, _guess, _already_used = true) do
  Map.put(game, :state, :already_used)
end
defp accept_move(game, guess, _not_used) do
  Map.put(game, :used, MapSet.put(game.used, guess))
  |> score_guess(Enum.member?(game.letters, guess))
end

Thanks for reading this post and always share your Elixir tricks with the community.