A Ruby Developer’s Adventures in Elixir

mhz
mhz
Jul 12, 2020 · 9 min read

This is a lighthearted write-up of my experiences learning Elixir coming from a Ruby background.

It all began early last year. A co-worker was talking about this programming language called Elixir, and a bunch of cool things it did .

I had never heard of it, but it sounded very exciting so I decided to read up. I was eventually lead down a learning adventure over the past year.

It has been my introduction to the Functional Programming paradigm. It taught me to think about code in a different way, and expand my understanding about programming.

Starting from the “beginning”

I started from the middle. I setup a simple application with Phoenix and tried hacking together a small app. Nothing made sense and the code was behaving in very strange ways.

I was really confused, and although I could hack a few things together after much Googling around I had so many unanswered questions!

Feeling a tad daunted by the whole ordeal, I felt the need to go back to basics. I wanted to understand what I was hacking together, and why I had to do it a certain way.

Functional what?!

When I chose that book, I was paying more attention to the word “Elixir” on the title than the words “Functional Programming”. As I later learned, I had been paying attention to the wrong thing. It wasn’t Elixir — the language — tripping me up. It was the Functional Programming paradigm that I didn’t understand.

There is one key concept of functional programming that once sunk into my head, everything started making sense. It was the concept of immutable variables.

Immutable variables

Well, Elixir enforces variable immutability. This means in Elixir all variables are immutable. You simply can NOT change a variable after assignment. Period.

Thanks Boromir, I wish I had known that earlier!

Loops were never the same again

typical iterative loops in Ruby won’t work

Since the exit condition of these loops are controlled by checking the state of a mutating variable (i.e. the index a), once you have immutable variables those loops would simply not work. The index can’t mutate! Nothing mutates!

If you wanted to write loops, you must use *drum rolls* yes… Recursion.

Hello Recursion, my old friend
I’ve come to talk to you again
Because “For” and “While” are all leaving
Please do not give me a brain-beating

From your memory that was planted in my brain
Nothing remains
Since my years in college

All the loops in Elixir are made from recursion. The Enum module does recursion under the hood. The for comprehension in Elixir is just syntactic sugar. All loops re-curse!

Toss objects out the window

Immutable objects actually can be represented in a simpler manner by just using a struct to represent it’s variables, and name spaced functions. The below Elixir snippet is an example.

Elixir has a pipe operator which passes the result of the last function to the next one. It makes the invocation above prettier like so:

If I were to print out the result of each function call above, it would look like:

%Gemstone{color: "muddy", weight: nil} <- after .find
%Gemstone{color: "red", weight: nil} <- after .clean
%Gemstone{color: "red", weight: 5} <- after .weigh

It may seem like the Gemstone struct has been modified after each step, but actually a new struct was created every time with slightly different values.

The functional paradigm

output = mod3(mod2(mod1(input)))

It looks very much like a typical math equation.

y = p(g(f(x)))

Once I understood immutability and the constraints that it imposed towards the language, I finally understood 70% of the “why” questions.

However, there are some other features of this paradigm that I also had to learn. I have seen some of these patterns being adopted in Ruby as well.

Pattern matching

Pattern matching in Elixir is present everywhere. In a way, its similar to a conditional assignment, compressed into a very short expression.

One cool pattern matching feature that Elixir has is matching on a function’s arguments. For example, lets write a function to sort out the clean Gemstones.

There are 3 sort_clean functions but the arity of each one is still 1. Via pattern matching the input arguments, the program chooses in-order on which one to execute. Think of it as a different of writing the conditionals and variable assignments that would typically exist inside one function.

We can invoke the sorting function with some dirty gemstones like such:

Which will ultimately return the clean gemstone.

[%Gemstone{color: "red", weight: nil}]

Higher-order functions

As an example, the Enum.map function iterates over an enumerable value, but you can extend upon the “iteration” functionality by passing in other functions.

Concurrency & Parallelism

In that regard, it is parallel right out of the box. No need to worry about making tests run in parallel as an optimization.

There is a real nice article that explains how it works, and touches base on BEAM, OTP, scheduler, load-balancer, and such.

But wait! There is no way you can write any sensible piece of software without saving state!

Impurities

  • Managing data from a database (disk or memory)
  • Managing local files
  • Working with STDIN/STDOUT

But wouldn’t that hinder concurrency? Wouldn’t two concurrent processes dependent on the same state inevitably affect each other, say, having to wait for a write to finish during a read request?

Staying in my happy bubble

The core idea is you keep one process alive whose sole purpose is to manage a state it is responsible for, and reply/receive messages from other processes.

Each individual process that sends a message to the state-managing process gets a response [ok|error] immediately and is able to continue without having to wait.

What about race conditions?

This way the processes depending on the state don’t need to worry about writing invalid data. If you try to do that, the state-preserving process will stop you.

I need some supervision

Because, well, you can’t have a bunch of children and have them freely running around. What if one of them dies, or gets stuck, or gets lost? What if one of them misbehaves and needs to be brutally killed?

I would think that the supervision tree would also be very useful for the robustness of the system. I’m not exactly sure how that would work, but if the supervisor can restart a dead child automatically, that would help the entire system recover with minimal effect to other parts of itself.

source: https://www.researchgate.net/figure/A-simple-supervision-tree-for-a-robot-application_fig3_221063699

The journey so far

1. Protect pure functions

One way of doing this would be to use monads to help with control-flow in Ruby. The caveat here is that all the data coming from IO should be loaded before processing it and all the data that needs to get written back to IO should be sent after. The monad itself should be kept pure.

This way the function can be easily tested, and even a large set of writes can be atomic.

2. Recursion at arms-length

Seeing that most programmers in imperative languages don’t use recursion, it could even be a disservice to use recursion when not necessary since other members of your team will have an unnecessarily hard time understanding it.

With that being said, it is good to understand how it works, and know how to write a simple recursive function just in case the need arises.

3. Continue learning

Learning about other languages / paradigms helps to enrich my knowledge as a developer so I can tackle problems better in the future.

References and Resources

The Startup

Get smarter at building your thing. Join The Startup’s +800K followers.

mhz

Written by

mhz

Software Engineer. Using this platform to share knowledge on software, and reflections in life.

The Startup

Get smarter at building your thing. Follow to join The Startup’s +8 million monthly readers & +800K followers.

mhz

Written by

mhz

Software Engineer. Using this platform to share knowledge on software, and reflections in life.

The Startup

Get smarter at building your thing. Follow to join The Startup’s +8 million monthly readers & +800K followers.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store