Drink Me
June 17, 2014
My Critique of Elixir
First, let me admonish myself for having limited knowledge of the subject. I am far from an Elixir expert. So I readily defer to those with more experience that can make a solid contrary argument against my conclusions. However, as I am unlikely to continue on in my adventures with Elixir, this is clearly the only time I have left to opine on the topic. So I will do so, and feel free to take my opinions with as much salt as you feel necessary.
There are many things to like about Elixir. Most notable of them is it’s inheritance from Erlang and it’s improved syntax inspired by Ruby. Its Erlang roots bring so many great features to the table: true functional programming, rock solid stability, distributed computing, and so on. Anything that is good about Erlang, is likewise good about Elixir. On top of this, Elixir brings a much more pleasurable syntax and a few higher-level conveniences which really set it apart from Erlang. (Otherwise there would be no point but to use Erlang directly). But then there are the caveats.
On the minor end, Elixir’s code documentation is a bit odd. Documentation of modules is placed within the defmodule block, while documentation of functions are placed on the outside the def blocks. Why the difference? In addition there is no sigil at the beginning of documentation lines to clearly indicate a comment. If a documentation section were large enough, one could mistake examples for actual program code. Only by the magic of syntax highlighters are we saved from any such “at a glance” error.
Then there is the minor issue of the def prefix on the definition of everything. defmodule, defstruct, deftype, etc. One can understand the desire to avoid too many keywords, but surely module, struct and type would do much better, as they do in almost every other language, and can be avoided as keywords if need be by appropriate context.
A little more troubling is the awful block syntax. Despite all the inspiration form Ruby, Elixir did not adopt Ruby’s elegant block syntax. Instead we get a strange twist of fn keyword, an arrow -> and an end keyword, all within the argument parenthetical.
Enum.each(list, fn x ->
...
end)
It feels a bit more like Javascript than Ruby. Ironically, this ugliness doesn’t save us from a whole mess of seemingly extraneous do statements everywhere. Every def* and even every condition like if also requires a do to define the subsequent block.
if ok?() do
...
end
It might not seem like much, but when you write enough code and find yourself having to always fix missing do’s first off, you can’t help but feel as if your time could be spent on more useful endeavors.
On a higher level of critique, I think Elixir, like Erlang and their ML brethren, suffer from Overtype Syndrome, if you will allow me to coin a phrase. Because the language is both functional and so strongly typed, every method comes by way of a module. So instead of say, list.each{...} as in Ruby, one much specify where each is defined, Enum.each(list, ...). And because one must repeat these module names over and over, there is a naturally common practice of extreme abbreviation. In some fashion it’s nice that names are concise. Yet it can be a bit difficult encountering module names like IEx in passing. In addition, we end up with a whole slew of modules and types with which to contend. Of course this can be true of any strongly typed language, but it seems especially so for the ML branch of languages.
Finally, my last niggle is with functional programming in general. Honestly, it’s pretty awesome. But just as honestly, it is difficult. The lack of procedural techniques puts a significant onus on the functional language to provide a large set of easy to understand and easy to utilize functions to pipe together to achieve the desired result. Partly it is due to lack of experience in thinking functionally, but it is clear enough that even for very component functional programmers it can be a real challenge to piece together the right functions to solve a complex problem. I believe this is because functional programming forces you to think about the whole more so than procedural programming. With a procedure, one can quickly break down a problem into parts. e.g. “I have an array of strings, which I need to iterate over and extract each word, and for each word, make it lowercase, and then update a hash table to track the number of times the word occurs.” Easy enough. Functionally the reads more like, “I need to build a dict via a flat-map over a list of strings and the list of words in each while lowering the case of each word, with an updated key/value pair.” Or something like that. It is simply harder to even to talk in functional terms.