5 productivity tips for Elixir programming

A round-up of Elixir coding techniques

Jason Tu
3 min readOct 18, 2017
Image from Unsplash.

Elixir is a fun and elegant language, but it took me awhile to understand and become productive with the language’s constraints and tooling. In this post, we’ll go over a few techniques that I’ve discovered in the process of working on my first few Elixir projects.

1. Dialyzer is your friend

Type checking in Elixir is a kind of one-two punch. We rely on pattern matching and guards while figuring out a function’s API, and upgrade to typespecs once the API is reasonably stable. Typespecs can be confusing if you’re used to the static typing in Flow or TypeScript, but that’s because typespecs follow something called success typing; if at least one code path through your function abides by your typespec, Dialyzer (the tool that examines typespecs) won’t complain:

Learn You Some Erlang clarified a lot for me about Dialyzer’s behavior. Suffice to say, Dialyzer won’t always catch all type errors, but the errors it reports are always valid errors.

2. Railway-oriented programming

Railway-oriented programming is an apt metaphor for returning from a function early, but unlike most imperative languages, you can’t return early in Elixir. This might lead you to nest case statements within each other, in a similar way to callback hell in JavaScript:

While there are several solutions to avoiding “case hell”, the native Elixir solution (with statements) addresses this problem nicely. One can refactor the code above as follows:

Using with also makes it easy to handle error cases. You can provide an else expression and annotate errors easily:

3. Flatten your project structure

We conventionally structure a Mix project with config/, lib/, and test/ directories, and choose module names based on a module’s place in the project hierarchy. However, there’s nothing stopping us from customizing our project’s structure. In particular, I find it more elegant to place code files in the root of the project, and to colocate test files with code files.

Here’s an example of what I mean. Compare the conventional project structure:

To this flatter one:

We can switch to the flatter project structure by customizing :elixirc_file_paths and :test_file_paths in mix.exs:

Straying from community conventions like this may confuse other Elixir developers, but it’s nice to know that the option is there.

4. Detect circular dependencies at compile time

When using functions (not macros) from other modules, Elixir doesn’t force you to explicitly import them; as far as Elixir is concerned, module names are atoms and you’re just calling a function on an atom. But I find it useful to declare your module dependencies anyway, even if a module dependency has no macros. Take the following example:

In this example, Foo implicitly depends on Bar, and Bar implicitly depends on Foo. There’s a circular dependency between both modules, and yet it still works.

Although the example is a bit contrived, a circular dependency should raise the “code smell” alarm about your module design. If two modules are interdependent, why not combine them? Or perhaps pull some functions into a third module to break the dependency loop?

Luckily, we can lean on the Elixir compiler to detect these situations. By explicitly listing module dependencies, Elixir will refuse to compile our program:

Try compiling the two files above – Elixir will refuse to compile them.

Explicitly listing your module dependencies also makes them easy to swap out later with module attributes.

5. Modern Make

Last but not least, I find it convenient to abstract away common build tasks with Modern Make, an enhanced version of make(1) from TJ Holowaychuk.

Make has certain advantages. For starters, Make serves as an effective mnemonic. It might be hard to remember Mix tasks (at least for me), so writing down the most commonly used ones in a Makefile helps. Make task dependencies also help document best practices (such as testing and linting) that aren’t immediately apparent to new developers of your project.

With Modern Make, you can even refer to the same Makefile across projects. Check out mmake-elixir and this consuming Makefile as examples. Consuming Makefiles from other developers is as simple as “including” a path:

These tips are just opinions of course — I’m definitely no Elixir expert! But hopefully this was a practical look at wielding Elixir productively.

If you liked this post, feel free to follow the Dive Into Elixir publication. You can also follow me on GitHub and Twitter for Elixir-related projects.

--

--