Whenever there’s a discussion about Elixir, it soon becomes apparent that there’s still a lot of confusion regarding it’s purpose. Some developers have it in their heads that Elixir is merely some crazy new syntax that ex-Rubyists are using to avoid writing Erlang. Well, I’m going to try to dispell some of the myths and misconceptions with this blog post.
It’s not about syntax,
In The Excitement of Elixir, I responded to two of the most well-known criticisms of Erlang from real-world developers now that we have Elixir for comparison. I failed to drive home what Elixir truly represented. Elixir is not “just about syntax,” nor is that the only thing Elixir stands for. If Elixir’s goals were a pie chart, a friendlier syntax would represent a sliver of what makes Elixir a worthwhile investment.
Let’s nip this myth the bud right away: Performance of Elixir code should match or beat the performance of equivalent Erlang code. If you find that in your use case it doesn’t, you should immediately file it as a bug! Elixir, while incredibly expressive, still compiles to what the equivalent Erlang code would be. It’s still compiling to EVM bytecode at the end of the day. An Elixir function call is an Erlang function call—there is no overhead! Elixir’s powerful metaprogramming capabilities don’t come from e.g. runtime dispatching, but the fantastically powerful compiler. All this magic happens at compilation time, before your code even has to run. And this is the part that may blow your minds: Elixir will beat the performance of Erlang in some cases. We’ll get to that later, though.
I’m done arguing: Elixir is strongly homoiconic.
1 2 3 4 5 6 7 8 9 10 11 12 13
Not only is it homoiconic, but it has one of the most powerful macro system I’ve been able to find in among other macro-capable languages I’ve used to date. There has been volumes written about the expressiveness and value of macros in Lisps. I don’t think I need to reiterate what’s already been written on that front, but FUD is nevertheless still disseminated.
A great, simple macro to briefly showcase what they look like would be the
match?/2 macro from Elixir’s
1 2 3 4 5 6 7 8 9 10
It’s just incredibly easy to reason about what’s going on when you see it used in examples:
1 2 3 4
Let’s just clean that up with some partial application:
Macros are scary
Yes, they’re as scary as they are powerful. However, they’re the least kind of scary macros. Elixir macros are hygienic. This means that variables defined in a macro won’t interfere with variables defined in the local scope when you use the macro. Oh, and guess what? They are optionally unhygienic as well if you’re into that kind of thing. Oh, and you don’t have to lose line information either.
But…but macros can be hard to debug and can lead to rabbit holes of blah blah blah…
Yeah, we get it. Macros shouldn’t be abused. That’s not a fault with the language, but something the user has to strike a balance with themselves. If you ever find José (the BDFL of Elixir) ever advocating the use of a macro when a simple function would do, I’d eat my own shorts.
Okay, but just how metaprogrammable are we talking?
Metaprogrammable enough to do this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
What just happened?
We created a module that uses HTTPotion to download the public domain mime.types database from the Apache project, parses it, and creates a polymorphic function called
is_valid?. This is exactly how
String.Unicode is implemented in Elixir: it reads from an in-repo copy of the Unicode 6.2.0 database and statically compiles functions based on that database into the module. Extrapolate this power to anything, really. The public domain tzdata database for a timezone module, a currency database module, SQL query pre-generation, etc. This expressivity makes writing faster code easier, with less LOC.
The standard library and runtime
The Elixir standard library and runtime is where Elixir is really differentiating itself from Erlang. The Elixir standard library aims to dramatically increase the productivity of Elixir developers, while providing the extensibility and features Elixir developers expect from such a metaprogrammable language. A lot of Elixir newcomers really do themselves a disservice by merely wrapping functions in the Erlang stdlib to get a prettier module name and only adding a level of indirection. If your Elixir modules only wrap Erlang modules without provinding any real benefit, you’re doing it wrong. This is why Elixir interfaces to Erlang data types try to improve upon the Erlang interfaces by standardizing a noun-first API, providing enumerable support, enhancing with greater functionality, among many other things.
A brief glimpse at the standard library and runtime
Inspired by Clojure protocols, Elixir protocols allow polymorphic interfaces to Elixir data types and user defined records. My favorite protocol is probably the simple
EXN.Encoder protocol to represent a subset of Elixir for data representation:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
1 2 3 4
Enumerable protocol is based on reducers (inspired by Clojure Reducers), a functional, composable way to enumerate over your collections. Reducers allow for user defined enumerables to implement their own faster enumeration and also allows for lazy and parallel enumerations.
HashDict is just such a great example of what Elixir’s standard library is trying to achieve that I want to mention it. Erlang has several dictionary-like modules for storing key-value pairs, each with their own performance characteristics depending on the size of the data set:
gb_trees. It’s up the programmer to profile and choose the best profile guided implementation for the data size they think their data set is going to need to remain performant.
HashDict takes care of that for you automatically. Not only is it faster than the Erlang alternatives in almost any scenario, but
HashDict will dynamically scale the underlying storage mechanism to be the fastest possible for the data set it’s working with.
Unafraid of change
The great thing about the Elixir standard library is that with each release it can provide features that Erlang developers clamor for everyday. We have Erlangers, Clojurists, Haskellers, Rubyists, and Pythonistas trying to incorporate useful features into Elixir every day. Elixir isn’t afraid of introducing functionality that improves the lives of Elixir developers, and everything is on the table: new data structures, real Unicode support, anything.
The key to many developer’s hearts is tooling, and José understands this. Elixir tries to make tooling a big priority with the tools it provides.
A slight peek at the tooling
Everything is an expression:
1 2 3 4 5 6
- Coloring! (Not pictured here.)
- User defined helpers!
- And more! What more could you ask for?
Elixir takes inspiration from Python in the form of doctests. Interactive
iex sessions are embeddable in Elixir’s first-class documentation and runnable from your ExUnit tests with a simple call to
@doc with doctests from the
nil?/1 macro in the
1 2 3 4 5 6 7 8 9 10 11 12
That IEx session is turned into two test cases run with the rest of your tests when you type
A fantastic build (and soon deployment) tool. Inspired by the much beloved Leiningen and centered around the notion of
Mix.Tasks, Mix is by far the most pleasant answer to tooling I’ve worked with other than the awesome Leiningen.
Because I’m beginning to get lazy, I’ll just do a
mix help for you:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Elixir : Erlang :: Clojure : Java
Elixir isn’t the CoffeeScript of Erlang just as Clojure isn’t the CoffeeScript of Java. Just like Clojure, Elixir is more than a pretty face. Elixir is the power of it’s tooling, the expressiveness of it’s metaprogrammability, and the expansive feature set of it’s standard library while maintaining complete compatibility with—and heavily leveraging—OTP. Once again I have yet to adequately scratch the surface of what makes Elixir special, but I have more Elixir to write!