Elixir's documentation states that
In addition to the Elixir file extension .ex, Elixir also supports .exs files for scripting. Elixir treats both files exactly the same way, the only difference is in intention. .ex files are meant to be compiled while .exs files are used for scripting, without the need for compilation.
But I'm still not sure when to use which file type. What are the downsides and the purpose of .ex and .exs?
.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
.
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.
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:
.ex
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 issuemix
– .ex
for custom Mix tasks, or as data in mix.exs
(for simple task aliases).exs
and run via elixir 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:
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.test
– this shadows the builtin Mix task to perform some custom local-database setuptodo
– output search results for TODO comments in our (Elixir) codeSome 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/instancesI 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
).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With