rishenko's menagerie
Oct 29, 2018 • 17 min read

Fun Times with IEx

Today I wanted to mention just a few of the not widely discussed things that can be done with elixir’s interactive shell, IEx. Hopefully one or more are new to you and can help improve your development workflow.

Saving Shell Command History

If you’ve used iex before, you have probably noticed that it does not save your shell history. For instance, you start an iex shell, execute a few commands, shut it down, then bring it back up. You hit the up arrow on your keyboard and nothing happens.

Luckily, there are a couple ways to remedy this situation. First, you can specify it by hand as you start up iex:

iex --erl "-kernel shell_history enabled"

The alternative is to add the ERL_AFLAGS environment variable to your standard UNIX/Linux/Windows shell:

Interactive Shells, Mix Tasks, and Environments

You probably know you can run all mix tasks in an IEx shell:

You can on a per environment basis. Provided you have a specifically named environment config file in your project’s config folder, you can run your tasks in the shell for each environment. Using the above examples:

Gracefully Shutting Down IEx

Most people are familiar with using Ctrl+C to bring up IEx’s shell break menu, and either hitting Ctrl+C again, or choosing a for abort. However, there are times when aborting or abruptly killing your application can lead to corrupted data or files, such as when using Dets. In that case, you need a different approach, one that will gracefully shut everything down.

That’s when you use elixir’s System.stop/1 function to carefully shut everything down in your application:

> iex
Erlang/OTP 20 [erts-9.3.3.3] [source] [64-bit] [smp:12:12] [ds:12:12:10] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Interactive Elixir (1.6.6) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> System.stop()
:ok
iex(2)>

>

Note that using System.stop(0) is the same as calling erlang’s init:stop(). Here’s erlang’s official description:

All applications are taken down smoothly, all code is unloaded, and all ports are closed before the system terminates by calling halt(Status).

Useful Shell Commands

Though there are many useful shell commands provided by IEx and IEx.Helpers, I’ve listed a few below that I use on a regular basis:

r(module) - reload modules at will

IEx.Helpers.r/1 reloads a given module inside IEx. This is very usefiul when rapidly iterating and checking changes.

# checking an error message
iex(6)> ExToolbox.is_prime?(-1)
** (ArgumentError) ExToolbox.is_prime?/1 only accepts integers
    ex_toolbox.ex:146: ExToolbox.is_prime?/1

# noticed important error information is missing from the message
# changed the error message to add "positive"

# telling Elixir to reload the ExToolbox module
iex(6)> r ExToolbox
warning: redefining module ExToolbox (current version defined in memory)
  ex_toolbox.ex:1

{:reloaded, ExToolbox, [ExToolbox]}

# checked the call again, looks good
iex(7)> ExToolbox.is_prime?(-1)
** (ArgumentError) ExToolbox.is_prime?/1 only accepts positive integers
    ex_toolbox.ex:146: ExToolbox.is_prime?/1

h(module or function) - show documentation for a module or function

IEx.Helpers.h/1 displays the coded documentation for a given module or function.

iex(1)> h String.upcase/2

                      def upcase(string, mode \\ :default)

    @spec upcase(t(), :default | :ascii | :greek) :: t()

Converts all characters in the given string to uppercase according to mode.

mode may be :default, :ascii or :greek. The :default mode considers all
non-conditional transformations outlined in the Unicode standard. :ascii
uppercases only the letters a to z. :greek includes the context sensitive
mappings found in Greek.

## Examples

    iex> String.upcase("abcd")
    "ABCD"

    iex> String.upcase("ab 123 xpto")
    "AB 123 XPTO"

    iex> String.upcase("olá")
    "OLÁ"

The :ascii mode ignores Unicode characters and provides a more performant
implementation when you know the string contains only ASCII characters:

    iex> String.upcase("olá", :ascii)
    "OLá"

iex(2)>

import_file(path) - load and execute scripts inside IEx

IEx.Helpers.import_file/1 executes the contents of the file as though you had typed them directy into IEx. This is great for applications where you have an *.exs file containing some script that you want to load at will inside IEx.

For example, let’s say that for a give math project, we often find ourselves working with a few sets of prime numbers. Instead of having to retype and rerun the call, we can instead put the code in a separate .exs file and import that when needed.

Example prime_seeds.exs file:

# Prime Seeding
prime_thresholds = [10, 50, 100]

threshold_map =
  prime_thresholds
  |> Enum.map(& {&1, ExToolbox.find_primes(&1)})
  |> Enum.into(%{})

:ok

And loading it with import_file/1:

╰─ iex -S mix run
Erlang/OTP 20 [erts-9.3.3.3] [source] [64-bit] [smp:12:12] [ds:12:12:10] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Interactive Elixir (1.6.6) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> import_file("priv/prime_seeds.exs")
:ok
iex(2)> prime_thresholds
'\n2d'
iex(3)> threshold_map
%{
  10 => [2, 3, 5, 7],
  50 => [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47],
  100 => [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61,
   67, 71, 73, 79, 83, 89, 97]
}

runtime_info(opts) - show runtime info for the current VM

Displays information on the current VM, including: memory, statistics, VM version, architecture and running applications.

iex> runtime_info

## System and architecture

Elixir version:     1.6.6
OTP version:        20
ERTS version:       9.3.3.3
Compiled for:       x86_64-apple-darwin17.7.0
Schedulers:         12
Schedulers online:  12

## Memory

Total:              71 MB
Atoms:              379 kB
Binaries:           500 kB
Code:               9895 kB
ETS:                508 kB
Processes:          48002 kB

## Statistics / limits

Uptime:             15 hours, 10 minutes and 8 seconds
Run queue:          0
Atoms:              14154 / 1048576 (1% used)
ETS:                22 / 2053 (1% used)
Ports:              6 / 65536 (0% used)
Processes:          50 / 262144 (0% used)

Showing topics:     [:system, :memory, :limits]
Additional topics:  [:applications]

To view a specific topic call runtime_info(topic)

iex> runtime_info :applications

## Loaded OTP Applications

compiler:           7.1.5.2             (started)
elixir:             1.6.6               (started)
iex:                1.6.6               (started)
kernel:             5.4.3.2             (started)
logger:             1.6.6               (started)
stdlib:             3.4.5               (started)

Showing topics:     [:applications]
Additional topics:  [:system, :memory, :limits]

To view a specific topic call runtime_info(topic)

Configuring IEx - Making Inspect Pretty and Infinite

When working in IEX, you will spend a lot of time reviewing the output of various function calls. While strings and numbers are simple to review, using inspect/1 on maps, lists, and tuple trees without using the pretty: true option can be downright painful. On top of that, inspecting large lists or maps can be even worse. IEx configurations to the rescue!

IEx.configure/1 lets you configure your IEx shell, including the results of inspect. Speaking of inspect, running this in IEx will set these options for as long as your shell session runs.

IEx.configure(inspect: [pretty: true, limit: :infinity])

You can see an example of it in action below.

iex> Enum.map(1..100, & &1)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42,
 43, 44, 45, 46, 47, 48, 49, 50, ...]

iex> IEx.configure(inspect: [pretty: true, limit: :infinity])
:ok

iex> Enum.map(1..100, & &1)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42,
 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62,
 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82,
 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100]

IEx.configure/1 can also configure the iex prompt, size of shell history, colors, and more.

IEx Startup Scripts - Per Project and/or Global

IEx has a special file called .iex.exs it looks for each time IEx starts up. It looks for that project’s root directory, or failing to find it there, in the user’s home directory. Whichever it finds first, it executes.

Having a global .iex.exs file lets you customize every IEx shell to your liking, as well as loading helper modules, functions, aliases, and imports.

Here’s an example of my global ~/.iex.exs:

# Default IEx configurations
IEx.configure(inspect: [pretty: true, limit: :infinity])

# Global area where helper modules are kept or linked.
lib_path = Path.expand("~/.elixir")
beam_path = lib_path <> "/.beam"
File.mkdir_p(beam_path)

# function to filter out any files in ~/.elixir that cannot be compiled
can_compile? =
  fn f ->
    !File.dir?(f) && Path.extname(f) in [".ex", ".exs"]
  end

# Compile and load helper modules stored or linked in ~/.elixir. These will be
# made available to every IEx instance.
lib_path
|> File.ls!()
|> Enum.map(& lib_path <> "/" <> &1)
|> Enum.filter(& can_compile?.(&1))
|> Enum.map(& c(&1, beam_path))

# global imports and aliases
import(System, only: [stop: 0])
alias ExToolbox, as: Tbx

A project-level .iex.exs file can be used to import your global .iex.exs and handle executing any project-local aliasing, imports, or assisting functions. Below is the .iex.exs file I use with ExDiceRoller. It changes regularly, depending on what I’m working on at that point in time.

import_file_if_available "~/.iex.exs"

# project specific imports and aliases
import ExDiceRoller.Sigil
alias ExDiceRoller.{Compiler, Tokenizer, Parser, Cache}

As you can see, using the .iex.exs file is a great way to store IEx configurations and more, such as the previously mentioned in IEx configurations for inspect.

Conclusion

IEx is a fantastic, deep, and very powerful shell that has a myriad of possible configurations and tools to aid in development. I only covered a small part of what’s possible, so I highly suggest perusing the Elixir docs for IEx and IEx.Helpers.

Post by: rishenko