We recently talked on IRC about a possible compiler optimisation, maybe this would be something that could interest you?
Erlang has the inline_list_funcs
compiler option that inlines some functions from the lists
module, like lists:map/2
. This can sometimes give significant performance benefit, since it’s capable of removing the anonymous function and, at the same time, allows you to maintain clean code without having to do by-hand recursion everywhere.
The downside is that now the lists
module can’t be safely reloaded - if the implementation changes, the inlined code won’t and this could lead to various subtle bugs (that’s why Erlang doesn’t do cross-module inlining beside those lists functions, and even that is hidden behind a flag). Fortunately that’s not a big issue if you don’t plan on using hot reloading at all, or you concede that you won’t be able to hot reload the standard library. This is a fair choice in many situations.
The idea is to introduce something similar for Elixir’s Enum
module. The problem is that Enum
is polymorphic and we’re able only to inline the cases where we operate on lists. Fortunately, there are many situations where we know a value will be a list at compile time - the biggest source of that knowledge are chained Enum
calls - they always return lists, no matter what was the initial enumerable. Another case we know we have a list is when faced with a is_list/1
guard.
Since this would give a similar reloading tradeoff, if would need to be hidden behind a compiler flag or, as is the case with Erlang, a module attribute.
Probably the easiest way to implement this would be to turn the Enum
calls in places we know will use lists into calls to the appropriate functions from lists
module and turning on the Erlang optimisation. There’s also a possibility of experimenting with a more aggressive approach replacing, for example, an Enum.map
call with something like:
Enum.map(value, fun)
# turns into
case value do
list when is_list(list) -> :lists.map(value, fun)
other -> Enum.map(other, fun)
end
Of course it would be necessary to measure if such changes indeed give the expected performance benefits and check the code bloat that the inlining provokes. Fortunately delegating the bulk of work to the erlang compiler would allow us to follow best practices established there, for free.
A similar inlining could be done for some functions from the Map
module - things like Map.get/3
translate cleanly into a single case
expression. There is no polymorphism issue in that case, the only thing to consider would be making sure we raise the same errors on bad values.