Few years ago when I yet worked with legacy Rails apps, I heard about Elixir and Phoenix and their similarities to Ruby and popular open-source framework. I wanted to expand my skillset, and learn something new, believing that will make development more of joy. Functional programming sounded attractive, because I have never written functional code, except a few things in Ember or React - if that counts. Also immutability - I was hoping for less painful debugging. Last but not least this language was not mainstream, and I’ve been rather targeting niches so far. I checked out Elixir and here is my short write-up about my experience.

My Elixir Path

The few first months of learning Elixir and functional programming were about writing small prototypes. My friend challenged me with his pet project idea and I asked him if I can do it in Elixir. Sometimes it was a struggle because it was a solo journey, but it was worth doing exercise, just to explore the ecosystem. I also joined Slack group and asked questions, when I’ve been stuck. Folks in the community were helpful many times. They clarified my thoughts, and allowed me to get moving again.

Joining an Exercism helped me to speed up with grasping concepts of functional programming and solve more advanced problems - basically I tried to collect all medium level challenges, doing them regularly. It was quite good to have access to the solutions of experienced developers from which I could learn how I could do it better. This path of learning gave me the most fun (read challenge). Here is an example of how complex problem can be solved with less than 70 lines of code (are you football fan?!):

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
defmodule Tournament do
@win %{mp: 1, w: 1, d: 0, l: 0, p: 3}
@draw %{mp: 1, w: 0, d: 1, l: 0, p: 1}
@loss %{mp: 1, w: 0, d: 0, l: 1, p: 0}
@doc """
Given `input` lines representing two teams and whether the first of them won,
lost, or reached a draw, separated by semicolons, calculate the statistics
for each team's number of games played, won, drawn, lost, and total points
for the season, and return a nicely-formatted string table.

A win earns a team 3 points, a draw earns 1 point, and a loss earns nothing.

Order the outcome by most total points for the season, and settle ties by
listing the teams in alphabetical order.
"""
@spec tally(input :: list(String.t())) :: String.t()
def tally(input) do
input
|> Enum.map(&String.split(&1, ";"))
|> Enum.filter(&valid?/1)
|> Enum.reduce(%{}, fn(row, acc) -> score_match(row, acc) end)
|> sort_by_points
|> pretty_print
end
defp valid?(row) do
length(row) == 3 && Enum.at(row, 2) in ["win", "loss", "draw"]
end
defp score_match([home, away, "win"], acc) do
acc
|> Map.put(home, update_scores(Map.get(acc, home), @win))
|> Map.put(away, update_scores(Map.get(acc, away), @loss))
end
defp score_match([home, away, "loss"], acc) do
acc
|> Map.put(home, update_scores(Map.get(acc, home), @loss))
|> Map.put(away, update_scores(Map.get(acc,away), @win))
end
defp score_match([home, away, "draw"], acc) do
acc
|> Map.put(home, update_scores(Map.get(acc, home), @draw))
|> Map.put(away, update_scores(Map.get(acc, away), @draw))
end
defp update_scores(nil, new_scores), do: new_scores
defp update_scores(current_scores, new_scores) do
Map.merge(current_scores, new_scores, fn(_k, v1, v2) -> v1 + v2 end)
end
defp sort_by_points(scores) do
Enum.sort(scores, fn({_, score1}, {_, score2}) -> score1.p >= score2.p end)
end
defp pretty_print(scores) do
["Team | MP | W | D | L | P"]
++ Enum.map(scores, &print_line/1)
|> Enum.join("\n")
end
defp print_line({team, score}) do
"#{String.pad_trailing(team, 31)}| #{score.mp} | #{score.w} | #{score.d} | #{score.l} | #{score.p}"
end
end

Above you can see pattern matching in action (score_match), pipe operator inside tally and Enum.reduce/3 which I used very often in my Exercism solutions. Notation of Elixir functions was a bit weird for me on the beginning. Simply, Elixir has the same function taking different number of arguments, unlike Ruby. So we have Map.merge/2 and Map.merge/3 which can resolve conflicts through the given fun, see update_scores.

Output:

1
2
3
4
5
Team                           | MP |  W |  D |  L |  P
Devastating Donkeys | 3 | 2 | 1 | 0 | 7
Allegoric Alaskans | 3 | 2 | 0 | 1 | 6
Blithering Badgers | 3 | 1 | 0 | 2 | 3
Courageous Californians | 3 | 0 | 1 | 2 | 1

It ain’t magic

After moving from Vim to VSCode I was missing good support for Ruby and was constantly battling with my editor when working on larger projects, especially those which had a client part inside an Rails repo. I found extension called ElixirLS, it was super handy. Compiler warnings and errors (undefined functions) have been an enormous help as well. Compared to Ruby, where you get errors at runtime, it has been an advantage. In general, less magic is happening in Elixir/Phoenix in my opinion (thanks to immutability, Ecto syntax closer to SQL, some folks say - more modular framework). It means fewer places in how magic behaves to bite you. To clarify what I mean, have a look at this example from old loved Ruby:

1
2
3
4
5
6
t = Time.now
=> 2021-11-11 11:55:12.610773 +0100
t.utc
=> 2021-11-11 10:55:12.610773 UTC
t
=> 2021-11-11 10:55:12.610773 UTC # Oh, really!?

How about tools?

Elixir does not have so many libraries as Ruby (which can be good! - less choice is more happiness ;)). Packages that are available are mature (with a few exceptions of course!). Sometimes (well, often!) you will need to build a HTTP API client from scratch, or with partial help of existing packages. Check on how you can use functions that another developer has already implemented for your convenience, how to use macros in Elixir. I found that google_gax is using macros, for example, here: https://github.com/balena/elixir-google-gax/blob/master/lib/google_api/gax/model_base.ex#L63.
I believe you will get productive quickly with a solid Ruby (Python, JS, Java, or Erlang :)) background. If you need concurrency and performance, Elixir would be a great option too, however that’s not the topic of this post. Also, worth mentioning types checking on compile time and tools like dialyzer and credo.

For anyone interested in learning Elixir, I would recommend thinking of a project that excites you, rather than doing tutorials. By having a goal to build a prototype, you will be more motivated than as you would by copy-pasting the code. Elixir is niche language however I hear that more systems are migrated from rb to ex.