Engineering


Background: the example
Let’s use the <code>Application</code> module in a standard phoenix app as an example. It contains a list of child processes to be started with the application like this:
This list is unconditional, all children are always started. After adding some features, I needed to start some children only in some cases, like a task scheduler or an OpenID Connect worker process, which might not be necessary for local development.
First, I attempted to take the standard children list and conditionally concatenate the other children to it. But the order of list elements determines the startup and shutdown order, and the <code>Endpoint</code> entry has to come last, so I needed several sublists. This approach was cumbersome. Finally I came up with a list of possible children, where each is a tuple of <code>{boolean, child_spec}</code>:
That way I can easily specify all child processes in the correct order. The ones that must always be started have a hardcoded <code>true</code> as their first tuple element, the ones that are conditional use private helper functions like <code>start_scheduler</code>? (not shown here) to determine whether they should start.
Now, all I need to do is to transform this list into the final list of children: filter all list elements where the first tuple element is <code>true</code>, and then map all list elements to the second tuple element.
Solutions
There are several ways to achieve this goal. If Elixir just had a <code>filter_map</code> function, like Ruby has! It turns out that at some point in time Elixir did indeed have such a function, but it got deprecated with Elixir version 1.5, in favor of combining other existing functions.
1. Chaining Enum.filter and Enum.map
This one uses filter/2 and map/2 in a straightforward manner:
I always feel a little uncomfortable reaching into tuples with elem/2. This solution can also be written in another way by pattern matching on the tuple:
And, just to be complete about this, it can also be written with <code>Stream.filter</code> and <code>Stream.map</code>.
2. Using Enum.flat_map and pattern matching on the anonymous function arguments
This one is an unconventional usage of flat_map/2 with a multi-clause pattern match on the anonymous function arguments:
3. Using a comprehension
This one relies on a comprehension with a filter:
It turns out this can be written even shorter, as the left hand side of the comprehension, the so-called generator expression, supports pattern matching and all non-matching patterns are ignored:
Conclusion
This was really fun, trying out different ways to build a list with conditional elements in Elixir! Personally, I like option 3 best, it is concise and elegant, and besides it neatly solves my application startup problem.