This story was originally posted by me on Medium on 2016-10-07, but moved here since I'm closing my Medium account.
Like many Elixir developers I come from Ruby. Elixir is my first functional language after years of Java, VB.Net, C# and Ruby. And a little bit of Javascript.
One of the first things that stroke me as being different in functional programming, was how I should map, reduce, loop and otherwise iterate data models.
The each
In Ruby we might find something like this:
def concat_messages(items)
result = ""
items.each do |item|
result += item.message if item.ok?
end
result
end
The issue of converting this to Elixir, is that everything is immutable in Elixir.
Trying to do the same thing would yield an empty result, since everything that happens inside Enum.each stays inside Enum.each. It’s basically the Las Vegas of iterations. You only bring home the actions with side effects, like writing to the console or getting married.
There are a number of ways to write the same code in Elixir. Let’s try with Enum.filter
and Enum.reduce
:
def concat_messages(items) do
items
|> Enum.filter(&Item.ok?/1)
|> Enum.reduce("", &(&2 <> Item.message(&1))
end
This looks really nice! It exposes the intention of the code and makes it more readable.
Couldn’t we do the same thing in Ruby? Yes, easily. The only problem is that a lot of the loop-like code with mutable state exists in Ruby code bases. Writing the Elixir code above in Ruby would give us this equivalent:
def concat_messages(items)
items
.select(&:ok?)
.reduce("") { |acc, item| acc + item.message }
end
Be a good Ruby programmer. Don’t mutate state when you don’t need to.
The loop
In many programming languages it is possible to loop
and break
out of the loop. Something like this:
def fetch_something
tries = 0
body = nil
loop do
status, body = make_external_http_call
break if status == 200
tries += 1
if tries >= 5
body = "Service not available"
break
end
end
body
end
Trying to get an overview of this code takes a while :( It can break out of the loop two different places, and it mutates state between loop iterations.
How could be better? Let’s force ourselves to immutability with Elixir and rewrite the code:
def fetch_something(tries \\ 0)
def fetch_something(tries) when tries < 5 do
case make_external_http_call do
{200, body} -> body
{_status, _body} -> fetch_something(tries + 1)
end
end
def fetch_something(tries) when tries >= 5 do
"Service not available"
end
This was a big improvement in the Ruby code too! Of course, the lack of pattern matching and guard clauses makes it not-as-nice as the Elixir version.
Conclusion
It might be hard at first to rewrite mutable, object-oriented iterations to an immutable functional language. For a long time it felt like I had to think backwards.
But for some reason, immutability forces you to write easy-to-read and concise code, which is clearly a long-term benefit of any software project.
I still write Ruby sometimes, and when I do, I tend to have a more functional code style than before I met Elixir. It helps me reason about my code and I avoid mixing logic and data. I have become a fan of the Ruby keyword module_function
which allows you to define a module with pure functions in it, much like in Elixir.
Elixir and immutability is here to stay, and I see a big future for Elixir coming up.
Top comments (0)