DEV Community

Cover image for 10 things in Elixir that confuse Ruby programmers - Part 2
Oleg Potapov
Oleg Potapov

Posted on

10 things in Elixir that confuse Ruby programmers - Part 2

I have previously covered 5 functional programming features of Elixir that may confuse ruby developers. This time I'll cover the remaining 5 and dive deeper into the language concepts and types.

Symbols and atoms

irb(main):001:0> :asd
=> :asd
Enter fullscreen mode Exit fullscreen mode
iex(1)> :asd
:asd
Enter fullscreen mode Exit fullscreen mode

Symbols in Ruby and atoms in Elixir look absolutely the same and are often used for the same purposes and, thus, cause the same associations. Both are associated with immutable strings. But wait, if (as we already know from the previous chapter) everything is immutable in Elixir, why would they have atoms in the first place?

Atoms are yet another legacy part that Elixir received from Erlang[1]. Even though they are very similar to strings and can be converted into them, it’s better to think of them as if they were not strings but constants with the value equal to the name. But what for a language such as Elixir has these constants? One of the use cases is obvious - they can be used as keys in maps and structs, and this use case works for Ruby symbols as well. In Elixir, however, atoms serve one more/yet another purpose: they are used as names for all named modules. It means that every time you define a module, you create an atom equal to its name:

iex(2)> defmodule MyModule do; end
{:module, MyModule,
 <<70, 79, 82, 49, 0, 0, 4, 60, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 166,
   0, 0, 0, 16, 15, 69, 108, 105, 120, 105, 114, 46, 77, 121, 77, 111, 100, 117,
   108, 101, 8, 95, 95, 105, 110, 102, 111, ...>>, nil}
iex(3)> MyModule == :"Elixir.MyModule"
true
Enter fullscreen mode Exit fullscreen mode

Now, when we know that atoms and module names are the same, it’s not that confusing to see Erlang modules calls (e.g. :math.pi()) in our Elixir code since they are just modules with lowercase names.

There is one more small difference between Atom type and Ruby symbols. As atoms are constants they will never be cleaned up by the garbage collector, while symbols will. That’s why it’s better not to create atoms dynamically in your Elixir program.

Return statement

What’s wrong with the return statement in Elixir? Literally nothing. Because there is no return statement in Elixir. As a matter of course Ruby programmers should change their coding style, as they can’t use one of the most common tools in Ruby. When a function should be executed from start to end there is no return statement needed in Ruby - a method just returns the last value. And Elixir follows the same concept: the last value is the result of the function execution. So the only purpose of the ‘return’ keyword in Ruby is to achieve the early return from the method breaking its execution. And there is no exact match for this in Elixir. However there are several ways to replace this behavior.

Commonly, parameters validation is the reason to use early return in Ruby. For example:

def increase_items(items, increase_by)
  return unless items
  return :error if increase_by > 5
  …
end
Enter fullscreen mode Exit fullscreen mode

In Elixir, you can achieve the same result with the help of pattern matching or guards[2]. The same code in Elixir might look like:

def increase_items(nil, _increase_by), do: nil
def increase_items(_items, increase_by) when increase_by > 5, do: :error
def increase_items(items, increase_by) do
  …
end
Enter fullscreen mode Exit fullscreen mode

Despite being powerful mechanisms, pattern matching and guards have some limitations: for example, the number of functions allowed to be used in guards is limited [3]. It means that complex domain related validation still should be performed inside functions. Elixir has several conditional statements and macros, like ‘cond’, ‘case’, ‘with’ or ‘if’, and it is worth getting familiar with all of them to use the right one in the right situation.

Switching the mindset could be of greater help though. If you create a habit to keep functions tiny, so that each of them is responsible for exactly one thing,you'll probably won't need early returns at all.

Loops

Ok, we can put up with the absence of the ‘return’ keyword but what about loops? Loops are one of the most common and most used concepts in programming. However, in Elixir there is no ‘while’, ‘until’, ‘loop’ and even ‘for’. Since Elixir is a functional language, it implements loops differently, mostly by means of recursion [4].

Let’s imagine an infinite loop of some commands handling that finishes when “exit” command is received:

loop do
  next_command  = get_command()
  break if next_command == ‘exit’

  apply_command(next_command)
end
Enter fullscreen mode Exit fullscreen mode

The same code in Elixir can look like:

def handle_commands do
  next_command  = get_command()
  apply_command(next_command)
end

def apply_command(“exit”), do: :ok
def apply_command(command) do
  …
  handle_commands()
end
Enter fullscreen mode Exit fullscreen mode

Luckily, rubyists, unlike some other imperative language coders, have an advantage here. As using iterators instead of loops is considered good practice in Ruby you are probably more accustomed to seeing ‘each’, ‘map’ and ‘times’ much more often than ‘while’ or ‘loop’ in the ruby code. Additionally, Elixir has an awesome Enum module [5] that comprises all ruby iteration functions plus some extra features that do not exist in Ruby. This module covers most of common iteration problems and there is no need for custom recursive logic most of the time.

Keyword lists and maps

Let’s move to keyword lists and map structures in Elixir. In Ruby, you have hashes that cover all possible use cases for key-value data structures. Hashes can be converted to/from lists, have keys of different types and be passed to a method as keyword arguments with the help of double splat operator. So, it’s natural to expect other languages to do the same. But instead of Ruby hashes Elixir has two different associative data structures: keyword lists and maps[6].

The main thing to know about keyword lists is that they are literally lists, in which each element of the list is a tuple. This tuple includes two values- an atom that is considered a key and a value:

iex(1)> [a: 1] == [{:a, 1}]
true
Enter fullscreen mode Exit fullscreen mode

Since this type of structure is a list, the values are ordered and elements have linear access time, in contrast to the constant time for Ruby hashes.

Maps, on the other hand, are completely different. Like Ruby hashes, they can have any data type as a key and their values are not ordered, which means constant time access. Beside that, maps are much more flexible in pattern matching - it’s absolutely possible to match only one part of the map or just a single key-value pair:

iex(1)> %{a: my_var} = %{a: 1, b: 2}
%{a: 1, b: 2}
iex(2)> my_var
1
Enter fullscreen mode Exit fullscreen mode

Now it looks like there is no need to use keyword lists at all, but they are indeed used in Elixir even if you don’t notice them sometimes. For example, these are very common syntax constructions in Elixir:

def my_function(a, b), do: nil
Enter fullscreen mode Exit fullscreen mode

or

if (a > 0), do: 1, else: 2
Enter fullscreen mode Exit fullscreen mode

Both examples use keyword lists with blocks given for ‘do’ keyword, but as brackets are omitted, it feels like the built-in language syntax.

Function clauses

While very common in Elixir, multi-clause functions may seem confusing to adepts of other languages. The idea of having several variants of the same function exists in different languages, however, the implementation differs. In some of them, there is method overloading that depends on the number of arguments, some languages support template methods or classes that vary in application depending on data type. In Elixir, functions with multiple bodies are based on the pattern matching mechanism which makes them more flexible than anywhere else. Function clauses are attempted in the order, from top to bottom, until one of them matches the given arguments. Thus, functions can be split into clauses depending on the arguments’ type or values.

Ironically, the problem with function clauses is not to simply learn how they work, but to get used to applying them in your code. In Elixir, clauses can be a way to move the control flow (conditions, loops) out of the function body. They are also widely used to make recursive calls. Writing succint multiple-clause functions is a habit that comes with practice.

Conclusion

Although Ruby and Elixir syntaxes share many features, their bases are different. Ruby essentially is an interpreted language, executed by the Ruby interpreter. Elixir is based on the Erlang virtual machine called BEAM, which means that it operates with the same types. The main difference, however, is that Ruby is based on the OOP paradigm, while Elixir is a functional programming language.

And while familiar syntax may make reading a program written in an unfamiliar language easier, writing your own program will certainly require more time and effort, since you have to make sure that you don't unconsciously replace the original concepts and constructions of the "new" language with ones you are accustomed to applying in your "first" language.

Links

Top comments (2)

Collapse
 
katafrakt profile image
Paweł Świątkowski

Cool idea for the article and great content. However, I'm not sure about this part on atoms:

That’s why it’s better to create atoms dynamically in your Elixir program.

Shouldn't it be the other way around? It's better to not create them dynamically, as they will never be garbage collected.

Collapse
 
oleg_potapov profile image
Oleg Potapov

Thank you for the comment Paweł!
You are absolutely right, that was the idea. I will update the post.