A Collection of Tips for Elixir's Interactive Shell (IEx)
Something very fundamental and part of any Elixir developer's day-to-day workflow is Interactive Shell (IEx). The focus of this article is to present a collection of tips I find very useful while using IEx.
I will be using IEx version 1.14.3. Some of the things may be different or may not even work if you are using a different version of IEx.
In general, you can launch an IEx shell from the command line by typing -
iex
iex -S mix (for a mix based Elixir project)
iex -S mix phx.server (for a Phoenix project)
Throughout the article, I will show output from real iex sessions but in most cases, outputs will be truncated and annotated to suit this article.
Tip #1: Get Help
The first thing one should know after ending up in IEx shell is how to get help built into it like below -
iex(1)> h
IEx.Helpers
Welcome to Interactive Elixir. You are currently seeing the documentation for
the module IEx.Helpers which provides many helpers to make Elixir's shell more
joyful to work with.
This message was triggered by invoking the helper h(), usually referred to as
h/0 (since it expects 0 arguments).
The ‘h’ command prints a summary of available ways to get help. If you need help with a particular module you can do as below -
iex(2)> h(Enum)
Enum
Functions for working with collections (known as enumerables).
In Elixir, an enumerable is any data type that implements the Enumerable
protocol. Lists ([1, 2, 3]), Maps (%{foo: 1, bar: 2}) and Ranges (1..3) are
common data types used as enumerables:
iex> Enum.map([1, 2, 3], fn x -> x * 2 end)
[2, 4, 6]
iex> Enum.sum([1, 2, 3])
6
h(Enum) prints help regarding the Enum module.
For help with a particular function inside a module you can do as below -
iex(3)> h(Enum.reverse/1)
def reverse(enumerable)
@spec reverse(t()) :: list()
Returns a list of elements in enumerable in reverse order.
## Examples
iex> Enum.reverse([1, 2, 3])
[3, 2, 1]
Tip #2: Get Value of Past Expression(s)
Often time it’s useful to get the value of a command executed in the past. Especially, I often forget to assign the value of a long Ecto query or complicated expression and scrolling up using the arrow keys to go to the beginning of the expression to copy/paste can be cumbersome. So, this tip helps with that.
iex(5)> Enum.reverse([1, 2, 3])
[3, 2, 1]
iex(6)> reversed = v(5) <-- Assigns value from expression at line (5) to reversed
[3, 2, 1]
iex(7)> reversed
[3, 2, 1]
iex(8)>
In case, it’s the last expression whose value you need to access, a shortcut is just ‘v’ -
iex(8)> new_reversed = v <-- Assigns value from last expression
[3, 2, 1]
iex(9)> new_reversed
[3, 2, 1]
iex(10)>
Tip #3: Re-compile a Project or a Module
Often time I make changes in source code while in IEx session. To re-compile the full project in this case, you can do -
iex(4)> recompile
Compiling 1 file (.ex)
:ok <-- Prints :ok when something has changed and compilation successful
iex(2)> recompile
:noop <-- Prints :noop when nothing has changed
Sometimes I just want to compile a certain module, instead of the full project. In that case, the below works -
iex(7)> r MinitwitterWeb.PostController <-- Compiles and reloads module
warning: redefining module MinitwitterWeb.PostController (current version defined in memory)
lib/minitwitter_web/controllers/post_controller.ex:1
{:reloaded, MinitwitterWeb.PostController, [MinitwitterWeb.PostController]}
Tip #4: Search and Complete Command
Sometimes for long commands executed earlier, I need to search instead of scrolling or re-typing. IEx provides a nice shortcut Ctrl+r to go into search mode -
(search)`': r MinitwitterWeb.PostController <-- Ctrl+r puts in search mode
iex(9)> r MinitwitterWeb.PostController <-- TAB/Arrow puts it on a new row
Tip #5: Enable Shell History
IEx does not preserve shell history if you exit your session. But if you want to do so, you can invoke IEx as below -
iex --erl "-kernel shell_history enabled"
This will preserve shell history and will be available upon next launch. A useful thing would be to create an alias in your OS shell to launch this command automatically when iex is typed. For the bash shell, you can put the below in ~/.bashrc -
alias iex='iex --erl "-kernel shell_history enabled"'
NOTE: Someone on reddit pointed out you can achieve the same by just exporting below from your bash/zshrc file -
export ERL_AFLAGS="-kernel shell_history enabled"
This will enable history for your Erlang shell too.
Tip #6: Break Out of Expression
IEx provides a special break trigger (#iex:break) when encountered on a line by itself will force the shell to break out of any pending expression and return to its normal state -
iex(17)> defmodule Break do
...(17)> def break_out do
...(17)> #iex:break <- Breaks out of current expression
** (TokenMissingError) iex:17: incomplete expression
Tip #7: The .iex.exs File
IEx looks for a local .iex.exs file or a global ~/.iex.exs file loads the first one it finds (if any). You can read about it all here — https://hexdocs.pm/iex/IEx.html#module-the-iex-exs-file
In short, you can import certain modules, bind certain variables, or create certain aliases here that will be automatically executed in shell context.
I created a sample .iex.exs with two aliases as below -
alias Minitwitter.Accounts.User
alias Minitwitter.Accounts.Relationship
So, after invoking the shell, I can refer to them as User or Relationship since these aliases will be made available -
iex(2)> Relationship
Minitwitter.Accounts.Relationship
iex(3)> User
Minitwitter.Accounts.User
Tip #8: Load a Module or Script in IEx
The c/1 command compiles a file. While in IEx session, you can do the below to compile and load a module from the file -
iex(1)> c "lib/issues.ex" <-- Loads Issues module from this file
[Issues]
In the case of an Elixir script (.exs), it will compile and execute the file immediately. So you will see the output below -
iex(1)> c "./elixir_book.exs" <-- Compiles and executes this script
FizBuzz
Fizz
Buzz
Tip #9: Display Information
The i/1 command can be used to display information about an Elixir term. If I used this command in IEx shell for the previously loaded Issues module, it displays information as below -
iex(4)> i Issues <-- Prints information about this term
Term
Issues
Data type
Atom
Module bytecode
[]
Source
lib/issues.ex
Version
[74242156420198707021276849165021118249]
Compile options
[:dialyzer, :no_spawn_compiler_process, :from_core, :no_auto_import]
Description
Call Issues.module_info() to access metadata.
Raw representation
:"Elixir.Issues"
Reference modules
Module, Atom
Implemented protocols
IEx.Info, Inspect, List.Chars, String.Chars
If I run it on a previously loaded User alias, it displays the information below -
iex(1)> i User
Term
User
Data type
Atom
Raw representation
:"Elixir.User"
Reference modules
Atom
Implemented protocols
IEx.Info, Inspect, List.Chars, String.Chars
Tip #10: Print All Available Modules
Sometimes you may have to print all available modules in a shell to diagnose a problem. This can be easily done with the shortcut :+TAB.
Tip #11: List All Macros and Functions from a Module
iex(3)> exports Minitwitter.Accounts <-- Shows all functions and macros from Accounts module
authenticate_by_email_and_pass/2 authenticated?/3 change_user/1
create_user/0 create_user/1 delete_user/1
follow/2 followers/1 following/1
following?/2 following_ids/1 forget_user/1
get_user/1 get_user_by/1 list_users/1
remember_user/1 reset_hash/1 reset_user_pass/2
unfollow/2 update_user/2 valid_user?/2
Another nicer way to do it is -
iex(8)> Minitwitter.Accounts.module_info
[
module: Minitwitter.Accounts,
exports: [
__info__: 1,
authenticate_by_email_and_pass: 2,
authenticated?: 3,
change_user: 1,
create_user: 0,
create_user: 1,
delete_user: 1,
follow: 2,
followers: 1,
following: 1,
following?: 2,
following_ids: 1,
forget_user: 1,
get_user: 1,
get_user_by: 1,
list_users: 1,
remember_user: 1,
reset_hash: 1,
reset_user_pass: 2,
unfollow: 2,
update_user: 2,
valid_user?: 2,
module_info: 0,
module_info: 1
],
attributes: [vsn: [165218174362312500220516424562981707158]],
compile: [
version: '7.5.3',
options: [:dialyzer, :no_spawn_compiler_process, :from_core,
:no_auto_import],
source: '/Users/meraj/Development/test/elixir/Phoenix_Playground/1.4/minitwitter/lib/minitwitter/accounts/accounts.ex'
],
native: false,
md5: <<124, 75, 220, 233, 97, 246, 150, 35, 170, 236, 247, 9, 216, 63, 157,
150>>
]
With no arguments, module_info returns a keyword list, with keys: [:module, :exports, :attributes, :compile, :native, :md5]
. Any of those can be passed as arguments, to scope the return value. For example, you can do Minitwitter.Accounts.module_info(:md5) to just print md5 key.
Tip #12: List All Types from a Module
The t/1 command lists all types available in a module as below -
iex(4)> t Enum <-- Lists all types in Enum module
@type acc() :: any()
@type default() :: any()
@type element() :: any()
@type index() :: integer()
@type t() :: Enumerable.t()
Tip #13: Multiline Expressions
iex(15)> [1, [2], 3]
[1, [2], 3]
iex(16)> |> List.flatten()
** (SyntaxError) iex:16: syntax error before: '|>' <-- Error while pasting
iex(16)> ([1, [2], 3] <-- Works fine when surrounded by parenthesis
...(16)> |> List.flatten()
...(16)> )
[1, 2, 3]
In this article, I presented a number of tips that I think will be very helpful for any Elixir/Phoenix developer and make his/her life somewhat easier. I hope the reading is pleasant and useful. Happy Elixir coding!
References:
IEx official doc — https://hexdocs.pm/iex/IEx.html
Minitwitter app referred to in this article — https://github.com/imeraj/Phoenix_Playground/tree/master/1.4/minitwitter
For more elaborate and in-depth future technical posts please follow me here or subscribe to my newsletter.