If you're a Rubyist, you might have heard of Elixir: the new functional language with Ruby-like syntax created by José Valim, who used to be on the Rails core team. If you're curious about it and wondering if you should give it a try, this post is for you!
After playing with Elixir for about a month, I decided to write a post summarizing my thoughts. This post covers the following:
- Why I didn’t try Elixir earlier;
- Nitpicking at object-oriented programming and Rails;
- What changed my mind and made me give Elixir a try;
- Materials I used to learn Elixir;
- My thoughts after a month.
Why I didn’t try Elixir earlier
Elixir has made a lot of noise in the Ruby community. I heard it being praised many times when listening to varies Ruby podcasts; however, as an object-oriented enthusiast (and a Sandi Metz fan), I was skeptical about this new functional language.
I had the impression that functional programming wasn't for web development. The concept of using objects to model the world intuitively made sense to me. I believed using objects to encapsulate attributes, behaviors, and responsibilities was a better way to model business logic. My gut said with only functions and no objects, the codebase would be a mess. Being in the object-oriented world for so long, I simply couldn't imagine building web applications without objects. As some put it: "Functional programming is great for many things, but there are things which object-oriented programming fits better." To me, object-oriented programming fit web development better.
Besides that, I thought the biggest selling point for Elixir was concurrency, and I didn't care about that. Before starting Elixir, José was on the Rails core team working on making Rails thread-safe so people could leverage concurrency and utilize multi-cores. After lots of research, he realized there's no simple solution for concurrency with object-oriented languages. That's why he turned to functional programming.
Concurrency wasn't something I cared about. Yes, utilizing all the cores we have would be nice. But there were many ways to combat performance issues before concurrency becomes necessary. For many early-to-mid-stage startups, concurrency doesn't seem to be a requirement.
Nitpicking at object-oriented programming and Rails
As much as I believe in the S.O.L.I.D design principles, there are things about object-oriented programming that bug me.
First of all, as an application's domain complexity grows, determining the responsibility of each object becomes more difficult. Before writing a method, I need to think about which object should be responsible for it, and whether I should add it to an existing model or a new service class. As the app grows, the number of classes grows and the decision becomes harder to make.
Secondly, embracing the single responsibility principle (SRP) makes me think that perhaps functions should be first-class citizens. SRP says a class should have only one responsibility. As a result, we have lots of service objects each of which only does one thing. For example, we might have an
EventParser that parses events, an
EventValidator that validates events, and a
BalanceCalculator that calculates account balances. Most of the time these service classes have a public method and no attributes. The only time you instantiate a service object is when you need to use its public method:
BalanceCalculator.new.calculate(...). Or you can use class methods so you don't have to instantiate objects:
I don't have a big problem with this style of programming. It has been working great for us at work. But this style suggests that we want functions to be first-class citizens. In these service classes, we don't really need a class or an object. We simply want to use its function. Instead of
EventParser.new.parse(...), what we really want is
In terms of Rails, my biggest issue is its lack of domain boundaries. Rails is great for getting an app up and running in no time. But as the app grows, domain knowledge gets complicated. If you follow the default MVC structure, you will soon end up with bloated models and fat controllers. A codebase without good domain boundaries makes scaling an engineering team difficult.
None of what I mentioned is unsolvable. For example, with proper domain boundaries, a team of thousands of engineers can work on the same giant monolith Rails app and still be happy and productive. But it certainly requires efforts and discipline.
What changed my mind and made me give Elixir a try
Earlier this year, I went to the Taiwan Ruby X Elixir Conf to give a Ruby talk. At the conference, I chatted with many Elixir programmers and attended a few Elixir talks. There were three main things that made me try out Elixir.
First of all, I wanted to acquire a different way of thinking. At the conference, I had a long conversation with Taian Su and Yevhenii Kurtov about their transition from Ruby to Elixir. They both highlighted how learning functional programming had changed their way of thinking. The way Taian put it was: "You know how programmers reason about systems in an abstract way that many others don't? After practicing and getting used to functional programming, I find that I am now able to reason about programs from an even higher level of abstraction." He also mentioned this ability to think from a higher level of abstraction even helped him write better Ruby code. I was sold on the spot. I always seek the ability to reason about programs and systems from different perspectives. I might never use Elixir on a fulltime job, but gaining a different perspective alone is worth it.
Secondly, some developers said after getting up to speed with Elixir, they were more productive in using Elixir than in any other languages. Many Elixir developers were once Rubyists. Many of them had been writing Ruby for a few years or even over a decade. I was surprised to hear some of them said even though they had significantly more experience with Ruby, they were more productive in Elixir after getting used to it. They said the learning curve of functional programming could be steep but after a few months practicing, things started to clicked and the fun began.
Lastly, the Elixir team is addressing many things I care about. Coming from the Rails core team, José understands lots of the pain points web developers have. Similar to Ruby, Elixir cares about developers' productivity and happiness: "the language will continue evolving with productivity, maintainability and reliability in mind"
Materials I used to learn Elixir
I mainly used the following materials to learn Elixir.
Micropatterns by Cameron Price: a forty minute talk about learning elixir by doing small exercises. It also covers patterns that are common in Elixir.
The Little Schemer (first five chapters): a fun little book about recursion. I can't think of any materials that teach recursion better than this.
Elixir for Programmers by Dave Thomas: a great course that helps you hit the ground and running in no time. (I paused at the Phoenix section as I wanted to be more familiar with Elixir before moving onto Phoenix.)
My thoughts after a month
After playing with Elixir for about a month outside of work, I'm glad to report that I'm having fun and am excited about the future of the language. The following are some of my thoughts.
1. The Elixir community is amazing. Besides being extremely welcoming and supportive, the community is lively. There are lots of discussions that challenge the status quo and make us rethink the way we program. I have learned a lot by observing and participating in those conversations. For example, in this thread about building applications as a series of components, there were 27 developers that participated in the conversation with 93 replies. Participants include José Valim (the creator of Elixir), Joe Armstrong (the creator of Erlang, a language that "powers the internet"), a few popular Elixir library maintainers, and many Elixir users. It's great to see a group of developers with such a wide range of background coming together to have constructive conversations. I'm also excited that instead of blindly adopting standards from the past, the community challenges current assumptions and tries to find better ways to do things.
2. Many challenges we now face in web development have been solved in telecom. The new requirements we now have for web applications, such as scalability, resilience, near-zero downtime, observability, and performance, are nothing new to telecom. Erlang is a battle-tested 30-year-old technology designed by Ericson specifically to solve these challenges. Being a language on top of Erlang, Elixir inherits all the goodness from Erlang.
3. Elixir's lightweight processes change the way I think about servers and programming. Processes and threads give me the impression of being cumbersome and dangerous. I avoid dealing with them. But Elixir processes are different. They are extremely lightweight, self-healing, and easy to spin out. So much so that spinning out millions of processes doesn't sound scary at all. I said I didn't care about concurrency at the beginning of the post. But after working with Elixir, I'm surprised by how easy-to-use and powerful Elixir processes are. I now feel that I have this extra power at hand and can't wait to see how it will supercharge my programs.
4. Programming becomes about transforming data. When I code in Elixir, I focus more on getting things done and don't care much about allocating responsibilities. Functions, being first-class citizens, are not attached to any classes. Moving them among modules is almost hassle-free. As a result, when I write them, I don't worry too much about how to organize them.
5. The Elixir style of programming makes lots of sense to me. I'm glad that the Elixir style is similar to how I like to write Ruby. To name a few examples:
- The pipe operator (
|>) encourages you to break down a big function into small steps. When I code in Ruby, I do the same thing: break a big function into small methods and implement each of them.
- Pattern matching + guard clause on function signatures eliminate most conditionals (
case) and make requirements for the passed-in data explicit. I do similar things in Ruby. The first part of my function is normally dedicated to validating function inputs.
- Methods in Elixir tend to be small, and normally contain three to eight lines of code. I really love that.
The above is not necessarily unique to Elixir, especially when you consider external libraries. But going out of your way to "fight" with the original design of a language is never fun.
6. The language development has an intimate feedback loop. José runs an Elixir consultancy working with companies that use Elixir. As a result, José is able to hear feedback from Elixir developers and incorporate them into the design of the language. Many recent enhancements in the language are results of that. This healthy feedback loop convinces me that Elixir will be more and more delightful to use.
7. Functional programming and object-oriented programming share common ground. I'm glad to find some object-oriented design principles are also applicable to functional programming. Learning functional programming has deepened my understanding of object-oriented design as well.
After spelling out all of my excitement for Elixir, let's not forget that I have been using Elixir for only a month. And I haven't used it in any complex projects. One thing I'm certain about is: although Elixir might solve problems other languages face, it will also bring in its new set of challenges.
My hope is that by combining Erlang's power with a great leader and an enthusiastic community, Elixir might give us a chance to reshape the current landscape and take the joy and power of computing to the next level.
Some of my code got merged into Elixir's master branch. It's the first time I contributed to an open source project. I want to share how to contribute to Elixir in my next post. Subscribe below so you won't miss that! 😀
Enjoyed the article?
My best content on Elixir and Software Design. Delivered weekly-ish.
 More accurately, SRP means a class should have one, and only one, reason to change.  Another reason that shapes our style of programming is the Functional Core, Imperative Shell principle.  Here are a few examples showcasing Elixir working on the things I care about.
- The team is working on adding property-based testing to Elixir, as a way to uncover unknown corner cases.
- Phoenix, an Elixir web framework, introduces Context: "dedicated modules that expose and group related functionality." I hope this will guide developers to build applications with domain design in mind.
- A big feature for Elixir 1.6 is a code formatter. As small as it seems, it's a big deal. Sandi Metz explains why beautifully: "the ultimate cost of code is in its reading. It therefore follows that code should be optimized for readability, which in turn dictates that an application's code should all follow the same style." One of the formatter's design principles is "to provide as little configuration as possible. This eases the formatter adoption by removing contention points while making sure a single style is followed consistently by the community as a whole." I love that the Elixir team is opinionated and picks a stand for everyone.
Elixir hasn't solved all the development pain points. But I feel my voice has been heard and appreciate the team's efforts.