5 Tips for Elixir Beginners
I first started looking into Elixir upon the recommendation of a friend of mine about the wonders of the language. My first foray into the language was over a year ago as I dipped my toe into the Phoenix Framework to see how it compared to something like ReactJS, the web development framework I was familiar with. At the time, I was using it just to learn the language but was not truly dedicated to any particular project, so I soon fizzled out of learning it.
In December 2022 I decided that I wanted to start contributing more to the open-source community, so once again I reached out to my friend to see what open-source Elixir efforts were ongoing that I could turn my attention to. He recommended that I look into Scholar since I have a background in Machine Learning and it was a library that needed some attention. I contributed a very minor feature to the library. Despite how small the feature was, it exposed me to enough of the language to draw me into the point that I knew I wanted to find a way to contribute meaningfully. Since then, I have published two libraries in ML space (EXGBoost & Mockingjay) and am as excited as ever to see what the future holds for the language, and wanted to take some time to reflect on some of the most valuable lessons I have learned about Elixir.
1. Take Time to Understand the Basics
Although I like jumping head-first into new technologies, it is not always beneficial to do so. I have previously worked with languages that include functional features(mostly Scala), but never a truly functional language. Much of my initial confusion could have been avoided if I had taken more time to understand some of the foundational constructs of Elixir – namely the match operator (=
) (and pattern matching more generally), the capture operator (&
), the pin operator (^
), behaviours, and protocols. Not only will having a better understanding of these help you write better and more idiomatic Elixir, but it will also allow you to more easily read Elixir code, which is invaluable as you learn and explore the Elixir ecosystem.
I still remember my first "a-ha" moment with pattern matching, when I needed to take the first item out of a list of nested dictionaries. Instead of many calls or to a dictionary access followed by checks for a valid key, you can capture those three or four calls in a single pattern match. Similarly, pattern matches in function heads allow you to decompose complex control flow and make it much more readable. Rather than having a single function head for a function where the body does different things depending on the input, it makes more sense to separate those different behaviors into different function heads using pattern matching and guards. These features allow the code itself to be much more expressive. As the saying goes, it is better to have expressive codes than to have to add comments or documentation to explain what should be plainly obvious from the code alone.
Elixir provides comprehensive documentation and getting-started guides that will gently introduce you to the language, but also serve as great references as you get more advanced and need to refresh on the concepts. I recommend you read over the getting started guide once completely before doing much else in the language, and as you begin to write more Elixir and need to use more and more features of the language, then go back and reference the particular sections. For example, reading through the section on sigils will help when you are reading Elixir code and encounter the funky ~r
that you have never seen in any other language, and then when you find yourself wanting to implement your own sigil, you will remember that there is a nice section about them in the getting started guide.
2. Embrace the Amazing Community
If you still find yourself struggling to understand some of the aforementioned concepts, fear not! The Elixir community is very active across many different platforms such as Twitter, Slack (e.g. the EEF Slack), and the Elixir Forum. If you find issues on a particular repo, its contributors and maintainers are usually quick to respond and welcome Pull Requests from people outside of the project.
Many conferences such as ElixirConf and Code Beam make their talks available online for free, so even if you are unable to attend the events you can take full advantage of all of the educational material they provide. Many of the speakers at these conferences are authors of Elixir libraries or books that contribute valuable educational material for the language. Some of the most common books to refer to a new Elixir Developer are Elixir in Action by Saša Jurić and Programming Elixir 1.6 by Dave Thomas. An upcoming book by Stephen Bussey entitled From Ruby to Elixir is catered towards people coming from an object-oriented background to Elixir. Some notable Elixir podcasts are Thinking Elixir, Elixir Wizards, and Beam Radio.
In addition to consuming material from the Elixir community, it is important to also give back to the community, which is one of the best ways to learn. The more you engage with others the more you get to cross-pollinate ideas. When you put code out into the world you get a chance to have someone review your code. Each time someone takes time out of their day to review your code you will feel the satisfaction and vindication that will make you want to continue.
3. Treat Elixir as a Functional Language
Some of the features of Elixir can be likened to parts of object-oriented languages, but it is best to NOT try to shoehorn object-oriented design patterns into Elixir. I found myself doing this quite frequently as I was porting the Python binding of XGBoost into Elixir because I would read the Python code and not take time to step back to see what it was trying to accomplish and instead tried to find line-by-line analogs to Elixir. For example, in Python when you see an accumulator initialized before a loop, then perform an operation on it within the loop, and return the accumulator, thus mutating the accumulator each loop, you should be able to tell that you can just use Enum.reduce
to achieve the same outcome (in a matter of fact, the sooner you can start seeing most looping operations as forms of Enum.reduce
or Enum.reduce_while
the better).
Although behaviours and protocols exist in the language and are great tools to achieve similar characteristics that object orientation attempts to achieve, you should not always reach for those tools for the same tasks that you might reach for a new class in an object-oriented language. You should always attempt to solve your problem using functions, and only then reach for the other features of the language when functions alone cannot solve your problem (or using only functions greatly increases the complexity of your solution).
4. Learn Idiomatic Elixir
If you've ever written Python for an extended period of time or had to work with others in a Python project, it is likely that you've heard the term "writing Pythonic code." This refers to the fact that there can be various equally valid ways to write certain code, but one convention or standard is generally accepted by the community as being more correct, oftentimes due to taking advantage of more features specific to the language. Well Elixir is not immune to this same effect, with certain conventions being universally adopted by the Elixir community. I would argue that Elixir is even more prone to this over other languages due to the reliance on established Erlang packages and libraries (namely OTP) that predate Elixir itself.
The easiest way to learn idiomatic Elixir is to read implementation of standard libraries. If you're struggling to know the most idiomatic way to implement protocols in your application, perhaps look to the Enumerable
protocol from the standard library. If you're familiar with kwargs
from Python and wondering how you can implement similar behavior in Elixir, you can look at Elixir libraries and notice the use of keyword lists with a default empty list in the function signature. Although it is always best to understand why a convention is the way it is, sometimes it is best when starting to adopt the convention first and learn the why later, since it is likely that there are very valid reasons for it to be accepted that you might just not be aware of yet. Once you become more advanced at the language then you can more safely navigate when to deviate from the convention.
Certain practices are more concrete than other. For example, it is convention that any function named is_*
returns a boolean and can be used as a guard, whereas function names ending in ?
also return a boolean but cannot be used in guards. This is not enforced by the language itself but once you learn that rule it makes it easier to both read Elixir as well as write Elixir where others can share similar vocabularies.
Another pattern that you will see are function pairs where one return a 2-Tuple as a return type where it takes the form of {:ok | :error, value::string.t()}
, and the other returns the raw value. For example this convention is used throughout the Jason module such as with decode
and decode!
. The former allows the user to match on the first element of the tuple to handle success or error how they want, while the latter will "unwrap" the raw value and either succeed or throw its own error. The more Elixir you read and write, the more of these conventions you will begin to notice and adopt for yourself.
5. Get Familiar With Elixir's Rich Tooling Ecosystem
One of the best things about Elixir is that you also get all of the rich tools built for Erlang which long preexisted Elixir itself. During every step of development, there is likely a tool that can improve your efficiency and quality of life. Start by learning the build system Mix to create and compile your application, linting and static analysis can be done with tools such as Credo and Dialyzer, testing can be done via ExUnit, documentation is covered by ExDoc, and finally, you can publish your projects to Hex. Learning the technologies that make up the Elixir development stack is a great way to become more familiar with the language overall and each one is demonstrative of why developers find Elixir such a joy to work with.
The seamless integration between Hex and its documentation domain HexDocs along with ExDoc makes is intoxicatingly easy to publish packages in Elixir. Mix Tasks give way for developers to write (and distribute) their own modules for the Mix build system, which means that for nonstandard libraries there are often Mix tasks available that assist in your deployment, or you can even write your own to share with anybody else who might find it useful.
For example, Erlang has the NIF (Native Implemented Functions) library that allows code that is run on top of the BEAM (the Erlang/Elixir Virtual Machine) to call native code. When writing Elixir NIFs, you will need to have an accompanying shared library written in native code (such as a .dll
, .so
, or .dylib
) to link your NIF against. When publishing to Hex, it would be convenient for the end users of the library to not have to necessarily recompile those libraries every time they pull from Hex, so you might wish to have precompiled distributions of the libraries available for the most common architectures. Well, I had this exact case when writing EXGBoost, and I took advantage of the ElixirMake and CCPrecompiler libraries to precompile my NIFs and include them in my Hex distribution (the guide I followed to do this is here for those who are curious).
Conclusion
As with all things, our mileage may vary when beginning your Elixir journey, but I have found the whole language, from its developer ecosystem to the thriving community, to continue to draw me in and make me excited to be a part of it. Even more exciting is that the language itself and its surrounding communities are still quite young and technologically diverse! I have planted myself firmly in the Machine Learning camp of Elixir, but web development with the Phoenix Framework is one of the most beloved frameworks there is, and there is even open development on an embedded Elixir system from the Nerves project. There is vast opportunity across all of these domains for meaningful contributions even from beginner Elixir developers, so I would encourage everyone to find their niche and put code out in the world.