Written by Marcin Kasprowicz
Published February 13, 2023

Elixir for JavaScript developers: first impressions

What I genuinely value at my workplace is that I can easily explore new languages through internal mobility. Throughout my career within Schibsted, I have been building things with JavaScript, TypeScript, Go, Kotlin, and recently Elixir. Where do we use Elixir? At Helthjem, which is one of the companies under Schibsted’s umbrella. It’s a distribution company. We deliver packages to your doorsteps (if you are in Norway 😅). It is a domain with tons of events flying around, complex business flows, and the need for precise tracking of parcels. Our core software needs to handle it. People can get pretty angry when their packages are delayed or even if they don’t arrive. Am I right? At its essence, it must be robust. Elixir (as well as its older sibling — Erlang), is known for its virtual machine, namely BEAM. BEAM (with OTP framework) allows developers to build fault-tolerant distributed systems using lightweight processes and a preemptive scheduler. Elixir is also known for powering Phoenix/LiveView, one of the most loved web frameworks. This stack is often used in start-up environments or fast prototyping. It doesn’t mean it is not suited for production workloads or enterprise usage!

In case of the problems that we try to solve, CQRS/Event sourcing with Elixir represents a great fit.

In a few sentences, I squeezed a few concepts that could make you dizzy. I need to sadden you if you get excited and hope to learn more about them in this article. I won’t cover them today. I plan to start a series of articles about this technology seen from a JavaScript developer perspective, so more to come. Today we will focus just on the language itself. I am curious about your first impressions.

Code

Without more ado, let’s jump straight into the code. Often when you are introduced to a new codebase, you need to understand what (and how) business processes it describes. There will be time for the internals; this is what “they” say 😉. Assuming that we need to solve a problem: reverse words in a sentence… using the two-pointers technique.

input: "you must unlearn what you have learned"
🧑‍💻
output: "learned have you what unlearn must you"

Some Master Yoda wisdom.

A JavaScript function can be written like in the listing below. A Loop while with a temporary value for modifying words array by reference by index.

The same logic, but written in Elixir:

To run those functions, check out the repository and follow README.md . Node and Elixir required 😀.

If you are thinking why did that guy overcomplicate such a simple task the answer is: this is just a showcase of language features. You can solve that problem (without two-pointers) just by preparing a one-liner.

I’m pretty sure you can understand what code from the above listings does. If you don’t, please read it thoroughly and study as this is very important for our upcoming considerations. You should notice three things:

  1. Why is there no while loop?
  2. Why does function reverse_items have three definitions?
  3. What is that weird sign |>?

Where is while loop?

The JavaScript function and Elixir ones have been defined in modules. We have them in both languages. We use them as a namespace for your functions for the sake of code organisation.

Did I mention that Elixir is a functional language? JavaScript has some features that allow you to do functional programming. JavaScript allows you to do many different things… especially those questionable ones. This is why I consider this language pretty hard, as it is full of traps. Functions in both languages are treated as first-class citizens. You can assign a function to a variable, return a function, pass it as an argument, etc.

So, we have modules and can do functional programming, but what about that missing while loop? Loops are iterative constructs that don’t exist in functional languages. Instead, you use recursion or map&reduce.

Thank you r/ProgrammerHumor.

If you have been programming in JavaScript for a while, I bet you prefer iterate thought collections using map() and reduce(). Instead of for or while loops. Intuition tells you this approach is cleaner, easier to glimpse, and clearly shows the data flow.

Starting to think recursively is something that takes some time. To be more specific about what type of recursion I have used, I used a tail recursion. The fourth parameter of reverse_items(words, left + 1, right — 1, result) is result. It is a way to carry over the state to the next iteration. However, if I need to be honest, usually, you won’t see recursion in Elixir applications. The way to iterate over things is to use a map&reduce pattern. At least, this is what I do 😀.

Let’s focus on a line from the while block: words[left] = words[right];. What did I do here? I mutated the array words. Inside a given scope (while block), I modified some things that belong to an outer scope. We call behaviour like that a side effect. Elixir won’t allow you to do that. You won’t find any function or a way to modify an array “in place” in the language API. Functional languages don’t allow mutating data. There is no space for confusion like with the split() vs splice() functions in JavaScript. Just see how many results give you the term “JavaScript splice vs slice” in Google. I have always tried to avoid using these functions because I didn’t know which would mutate my array and which would not. To “update” a variable in Elixir, you could create a new list and then rebind it to the same symbolic name:

I just showed how to add an item at the beginning of a list and how at the end. But what about adding 13 in the middle of numbers to keep continuity?

Of course, you can do it. But with one important note. As you well know, in JavaScript, everything is an object. An array in JavaScript is an object. Pretty high level of data abstraction, isn’t it? In Elixir, we don’t have arrays; we have a List at our disposal. Underneath, lists are implemented as a singly linked list. So doing List.replace_at(numbers, 2, 12) is a linear operation — O(n). You should pick a more efficient data structure if you need to edit something by referencing some key — for example, a Map, which behaves like a hash table.

By the way… can you guess what time complexity is a numbers[2] = 12 operation in JavaScript?

Three definitions of reverse_item

I have the next puzzle for you 🕵️. What will be printed out by the following code?

If you don’t know what and why start reading about hoisting in JavaScript. What is my point here? The point is that “you can have” only one function with a given name in JavaScript. You can’t overload it.

Function definition in Elixir starts with a def (or defp if it is a private function), name, list of parameters, and the body enclosed in a do…end block. In the Elixir world, we use the following convention to refer to a function: <module name>.<function_name>/<arity> for example: Reverse.reverse_words/1. The arity of function refers to the number of arguments it takes. This is important, as two functions with the same name but different arities are two different ones.

Hold a second cowboy… but you have three definitions of function with the same name and the same arity. Yes, it’s true. You can overload functions in Elixir by specifying multiple clauses. This technique is used for creating conditional branching.

Let’s analyse all clauses, one by one.

The first one is not really a clause. It is a function head. Function definition without a body. We use it when a function has multiple clauses and default value(s). In our case, w did it for the result parameter. The default value of result is %{} (empty map). We do it like that to keep our code clean.

The first clause will be invoked only when the value of left is greater than or equal to right. I used something called a guard.

You might notice that there is no return keyword in the function body. The return value of a function is the return value of its last expression.

And the second clause… We call it the default clause as it matches everything that has not been “caught” before. We put it as the last one.

Controlling a flow by function overloading is one technique that makes code written in Elixir declarative. You probably have heard something about that. Usually, it is in opposition to imperative code. I like to think about declarative code that is easier to understand for a non-technical person. It is a fascinating topic that is hard to get an idea of just by reading about it. But once you understand, you will experience that eureka moment💡.

|>

This is the pipeline operator. It takes the output of the previous function and passes it as a first argument to the next one.

Piping in action.

To replicate the same behaviour in JavaScript you would go with the staircase style. If you care more, you will assign the result of consecutive functions to a temporary variable.

Staircasing in JavaScript:

And the same but with pipeline operator in Elixir:

You will see that this pattern is used quite often. It encourages developers to write composable functions. Which in the end results in a better code.

Summary

I tried to keep it short. I jumped from one topic to another one. I didn’t even mention pattern matching, which is the essence of Elixir. My goal was to sow the seeds of curiosity. If you are interested in more details, please check out https://elixir-lang.org. It is the best starting point for learning. In the Elixir ecosystem documentation is your friend. And this is what I mean! Many interesting topics to explore are ahead of you. I hope that in one of the upcoming articles, I compare virtual machines. I guarantee you that it would be a great read.

PS: If you want to join Helthjem specifically (where we do Elixir) check out this open position https://www.schibsted.pl/career/backend-developer-2/

Written by Marcin Kasprowicz
Published February 13, 2023