Editor’s note: This is part one of a short series. You can find part two here.
I’ve spent the last month and change working on a little side project.
The interesting thing about this one is that it’s written completely in Elixir. This is a post about my experience doing so.
Elixir is a language that came out of the heyday of Ruby on Rails about a decade ago. It was originally billed as “Ruby on the Erlang VM”, trying to blend the best of both worlds. Spoiler, I believe it has largely succeeded.
It’s still got quite a bit of Ruby influence, but overall, it’s much more functional. You will see maps, filters and reduce’s all over your code base.
From the Erlang side of the family, you’ll see a lot. There’s no mutability. Variables cannot be changed, you can only create new ones from combining or transforming existing. Pattern Matching is everywhere and it’s still quite powerful. You can trivially call down to Erlang primitives whenever you need to.
The Ruby influence is more in style and syntactic sugar. You’ll see your def
’s and end
’s and ->
’s. You can skip parentheses around function arguments if it doesn’t introduce ambiguity. There’s no early return, you always return the last expression. Map and list syntax will be fairly familiar. The @ prefix denotes attributes / decorators. Atons.
It’s got some quirks. The native list primitive is a classic linked list, not an array. This means that prepend is cheap, but count, insert, append is expensive. It also means you can do head/tail car/cdr operations quite easily. Keyword lists as a weird list/map hybrid.
The |>
operation is a particularly good example. To most newcomers, it’s a weird, inscrutable symbol whose operation isn’t easily intuited.1 Yet once you get used to it, there’s quite a bit of elegance to it, and even historical reference to the pipe operation in the shell.
For those new to the language it works like this: take the input of the pipe operation (x below) and pass it as the first argument of the target function of the pipe operator.
x
|> function(y, z)
is equivalent to
function(x, y, z)
The elegance comes from chaining these together like so;
x
|> function(y, z)
|> other_function(a, b)
|> yet_another_function(c)
If this is the last expression in a function you return the results of the final yet_another_function
You can also bind the final output to a variable like so:
final_output =
x
|> function(y, z)
|> other_function(a, b)
|> yet_another_function(c)
The pipe metaphor breaks down as you start with x, do a ton of transformation on it and your eyes have to scan way back to the beginning to see what variable you are assigning the final results to.
It kind of micro-example of the entire macro-experience of using Elixir:
To see an actual code snippet with pipes in action:
collected_errors =
Date.range(start_date, yesterday)
|> Enum.to_list()
|> Enum.filter(fn _ ->
random_number = :rand.uniform(100)
random_number < 75
end)
|> Enum.map(fn date ->
make_map_of_attrs(date, user, some_bool, some_int)
end)
|> Enum.reduce([], fn entity_attr, all_errors ->
case create_entity_with_attr(entity_attr) do
{:ok, _entity} ->
all_errors
{:error, err_msg} ->
[err_msg | all_errors]
end
end)
If the above looks like moon runes, it’s probably just because the syntax is new to your eyes. After a week or so with the language, it reads very clearly. Walking thru that snippet, we start with the second line:
create_
prefix means you are creating and object and often storing it in some persistence layer.all_errors
accumulator during the reduce operation.collected_errors
variable.I’m very, very fond of go, but a typical implementation of equivalent functionality would typically be at least 5x longer and certainly more irritating to write.
So, back to that macro experience of using Elixir:
That last bullet point, in my relatively short experience with Elixir, I’m fairly certain the overall developer productivity is quite high. Likely in the top quartile or better of all development environments (possibly even top 10%). There are others who think so as well.
The combination of the last two bullets is what I refer to as sublime efficiency in the title of this post. This is a rare combination. I believe go to be a great language for “IDE to prod” developer productivity, but I don’t believe it is a particularly elegant language. Lisp and it’s variants can be quite elegant but you will run into feature velocity problems at some point (deployment, ecosystem, hiring…).
Developing in a modern functional language, with great safeguards (pattern matching, guards, type annotations, strong testing libraries, etc.) somehow allows developers to be efficient, write elegant code and also achieve high feature velocity.
It’s clearly not a perfect language or ecosystem. The deployment story is not as good as go.2
But it is one that I could see a lot of companies using as a competitive advantage with respect to time to market of getting product out.
All you have to do is get over the learning curve.
Author’s Note:
This post is the first of a two part series. You can find the second part here.
Lastly, I do live code streaming about Elixir, Phoenix, and LiveView on twitch.tv. You should follow me there.
Footnotes: