Elixir: When to use .ex and when .exs files
For some more concrete details, here are some patterns or rules of thumb I've noticed about when to use .ex
vs .exs
files:
Almost all code goes in an .ex
file in my application's 'library' directory tree, e.g. under lib
(or maybe web
if the application is an old Phoenix webapp). The code can be called (if it defines public functions or macros) elsewhere in the application or from iex
. This is the default.
Probably the most important consideration for deciding between .ex
and .exs
is where (and when) I want to call that code:
- In the application itself –
.ex
- From
iex
(e.g.iex -S mix
) –.ex
, if the code is also (or probably will be) used by the application;.exs
for 'ad-hoc' scripts, e.g. for a specific bug or other issue - From the shell, via
mix
–.ex
for custom Mix tasks, or as data inmix.exs
(for simple task aliases) - From the shell, i.e. as a 'regular script' –
.exs
and run viaelixir some-script.exs
.
What may be surprising is that a good bit of code that starts as one of [2], [3], or [4], eventually ends up in [1] anyways.
And a really important thing to keep in mind is that it's EASY to move code from, e.g. an .exs
file to an .ex
file (or vice versa).
For [2], and for one application in particular, I setup some extra 'scripts mode' Mix environments for use with iex
. Basically, they run a basic 'dev' local instance of (parts of) the app, but connect to the production/staging DB (or other backend services). I have some helper functions/macros for these 'command environments' but mostly I just use application code to, e.g. query the production/staging database and fix/cleanup/inspect some data. If I do need to do something that's even a little 'involved' (complicated), I'll usually create an ad-hoc 'issue script'. It's much easier to write code in my editor than in iex
.
For ad-hoc scripts for [2], I have a dev/scripts
sub-directory-tree in one project and mostly just name ad-hoc scripts based on the corresponding ticket number (from that project's ticket system), ex. 123.exs
. Then, in iex
, I can easily run that script:
iex> c "dev/scripts/123.exs"
One nice 'workflow' is to just define a single (top-level) module in the script file:
defmodule Ticket123 do
...
def do_some_one_off_thing ...
end
Then, from iex
:
iex> c "dev/scripts/123.exs"
[Ticket123]
iex> Ticket123.do_some_one_off_thing ...
Something something
...
For [3], both task aliases in your project's mix.exs
and 'fully' custom Mix tasks are really nice.
Some example aliases from one project:
- Run 'all' migrations – this project has regular (
Ecto
) database migrations AND some migrations that we want to run 'manually' (i.e. using the 'scripts mode' environments mentioned above). We use manual migrations for things like long-running database queries/commands, ex. adding or modifying an index on a big table. - Deploy to staging/production
test
– this shadows the builtin Mix task to perform some custom local-database setuptodo
– output search results for TODO comments in our (Elixir) code
Some example custom tasks from the same project:
Mix.Tasks.OneProject.BootstrapServer
– some custom { configuration management / automated infrastructure deployment } codeMix.Tasks.OneProject.DbSetup
– pulls some configuration data from the Elixir app but mostly just passes that to a shell scriptMix.Tasks.OneProject.ImportTranslations
– downloads translations and other internationalization data from a third-partyMix.Tasks.OneProject.RollingDeploy
– custom deployment that uses something like a 'blue-green' model, but with a load balancer instead of two entirely separate environments/instances
I haven't used entirely 'regular' Elixir script files much at all in any project – to be called via elixir some-script.exs
. Probably the biggest reason not to, i.e. to just create a custom Mix task or an 'issue script', is that custom Mix tasks are nicely documented, i.e. via mix help
and mix help some-custom-mix-task
, and I really like working at a REPL (iex
).
Elixir will compile the whole .ex
file.
.exs
files are compiled as well but are meant to be executed when invoked. So, most use cases of .exs
files are to execute code immediately when called. Think of using .exs
files for testing, migrating data and running scripts. Think of .ex
files as being used for your application's main business logic.
Consider this example
.ex
sample
sum.ex
:
defmodule Sum do
def add(a, b) do
a + b
end
end
$ iex sum.ex
...
Interactive Elixir (1.9.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> Sum.add(1, 2)
3
.exs
sample
sum.exs
:
defmodule Sum do
def add(a, b) do
a + b
end
end
#within same file
IO.puts "The sum of 3 + 2 is: #{inspect Sum.add(3, 2)}"
$ elixir sum.exs
The sum of 3 + 2 is: 5
I check it in Elixir version 1.9.1 and both extensions .ex
, .exs
will be compiled with elixirc
. Another words, we get bytecode (.beam
file) in both cases.
.ex
is for compiled code, .exs
is for interpreted code.
ExUnit tests, for example, are in .exs
files so that you don't have to recompile every time you make a change to your tests. If you're writing scripts or tests, use .exs
files. Otherwise, just use .ex
files and compile your code.
As far as pros/cons, interpretation will take longer to execute (as elixir has to parse, tokenize, etc.), but doesn't require compilation to run. That's pretty much it - if the flexibility of running scripts is more important than optimized execution time, use .exs
. Most of the time, you'll use .ex
.