Are you coming to Elixir from another language with an interactive shell? There are a few specific things about Elixir’s interactive shell (IEx) to keep an eye on and make ourselves more efficient. Here they are.
Mix environment
Running iex
on its own is usually pretty limiting. To start the Elixir’s interactive shell for the Mix project at hand, we have to run it as iex -S mix
in the project directory:
$ iex -S mix
Once it is started in this way, we can access both dependencies and the project code itself.
If we don’t have a Mix project or we need to run something outside of it, we can use the c/1
function together with the file path to load the code or module into memory:
iex(1)> c "file_with_functions.exs"
Since you cannot effectively manage Elixir dependencies outside Mix I almost always create a new Mix project by running mix new
even if I want to write something very simple.
Command history
After running some code in IEx you might notice that there is no command history. This is indeed quite unfortunate because retyping everything is no fun at all.
Luckily we can enable IEx history by exporting Erlang’s -kernel shell_history enabled
option via ERL_AFLAGS
:
$ export ERL_AFLAGS="-kernel shell_history enabled"
$ iex
To have this always available we should put it to our shell profile file (such as ~/.bashrc
for Bash):
export ERL_AFLAGS="-kernel shell_history enabled -kernel shell_history_file_bytes 1024000"
While we are at it we can also increase the memory for the historical data with -kernel shell_history_file_bytes
option as shown above.
We can also use CTRL+R to start a search mode to find some previous expression.
Module compilation
Since Elixir files are usually compiled (only .exs
files do not get compiled), it’s necessary to recompile the module when we make some changes to its source.
Using the r/1
function we can recompile a specific module:
iex(1)> r MyApp.MyModule
There is also recompile
, which simply recompile all changed modules in the current Mix project. This is super handy when we edit some source code and want to try the changes without running iex
again.
iex(1)> recompile
Simply type recompile
, hit up arrow (if history is enabled) followed by ENTER and we are running the new version of the code we were running before.
Documentation
We can easily access documentation for any available module by invoking the h/1
function:
iex(1)> h(Ecto.Repo)
Ecto.Repo
Defines a repository.
A repository maps to an underlying data store, controlled by the adapter. For
example, Ecto ships with a Postgres adapter that stores data into a PostgreSQL
database.
...
There is also the open/1
function which can open the module source code in your desired editor (this needs to be set by using ELIXIR_EDITOR
or EDITOR
shell variable):
$ EDITOR=vi iex -S mix
iex(7)> open(Ecto.Repo)
Invoking the h/0
function without any argument shows documentation for IEx itself, including functions such as h/1
and open/1
.
Debugging
Sometimes the documentation is not gonna cut it and we have to roll up our sleeves and get to debugging. With break!
we can easily set up a breakpoint anywhere in the Mix project:
iex(1)> break!(MyModule.my_func/1)
iex(1)> break!(MyModule, :my_func, 1)
In case there is some recursion we can pass an additional argument for how many stops we want to make before stopping. There is also breaks/0
that lists all our breakpoints:
iex(1)> breaks()
ID Module.function/arity Pending stops
---- ----------------------- ---------------
1 MyModule.my_func/1 1
Once set we can call our module function and the execution will be stopped letting us inspect the environment:
iex(4)> MyModule.my_func("args")
Break reached: MyModule.my_func/1 (lib/my_module.ex:2)
1: defmodule MyModule do
2: def my_func(args) do
3: "#{args}"
4: end
pry(1)> args
"args"
To finish and start a new shell process we can call respawn/0
:
pry(2)> respawn
It will cleanly continue with execution as if we had never set up any breakpoints at all.
Getting previous expressions
Sometimes our expressions can go unwieldy and repeating them is a pain.
IEx has a solution for that
iex(6)> [1, 2, 3] ++ [4]
[1, 2, 3, 4]
iex(7)> var = v(6)
[1, 2, 3, 4]
v/1
assigns a value from a given expression. It takes the last one when no argument is given.
Inspecting failing tests
Although inspecting our functions is great it can be handy to inspect our failing tests.
We can drop IEx.pry
in the failing test at hand:
# In test somewhere
require IEx; IEx.pry
and run iex -S mix test
with the --trace
option like:
$ iex -S mix test --trace path/to/simple_test.exs
Inspecting the test was never so convenient!
Custom configuration
If you need to reconfigure IEx a little you can create a .iex.exs
file.
$ cat .iex.exs
alias Digi.Cache
defmodule H do
def create_admin_roles do
IO.inspect "Creating roles..."
...
end
end
As seen above, this can be effectively used to define aliases (We all have long module names somewhere, don’t we?) or simply handy modules that can help us with something. .iex.exs
can be globally located in user home (~/.iex.exs
) or local in the project root directory.
Here is a bit more on the custom configuration.
Printing integer lists
You might have been bitten before by the following behavior:
iex(1)> [27, 35, 51]
'\e#3'
This is the explanation from Elixir FAQ:
Pretty-printing of lists is done by using Erlang’s native function. It is designed to print lists as strings when all elements of the list are valid ASCII codes.
Solution? Either add 0
to the list (works directly, with inspect/1
and IO.inspect/1
):
iex(2)> [27, 35, 51] ++ [0]
[27, 35, 51, 0]
Or use inspect/1
’s charlists: :as_lists
option:
iex(3)> inspect [27, 35, 51], charlists: :as_lists
"[27,35,51]"