DEV Community

George Arrowsmith
George Arrowsmith

Posted on • Edited on

Elixir for Ruby developers: the three most important differences

This article is part of a series of extracts from Phoenix on Rails, a comprehensive guide to Elixir, Phoenix and LiveView for developers who already know Ruby on Rails. If you’re a Rails developer who wants to learn Phoenix fast, click here to learn more!

Many developers who learn Elixir come from a background in Ruby. This is hardly surprising: Elixir's creator, José Valim, was formerly a prominent Ruby developer, and Elixir has been attracting Rubyists since the very beginning!

But while Elixir's syntax looks like Ruby at a glance, you'll quickly realise that these similarities are skin-deep. The two languages are very different in their underlying designs, and Elixir often requires you to structure your code in a way that looks nothing like the equivalent Ruby. Certain aspects of Elixir will feel very unfamiliar to a Rubyist, and will take some getting used to.

This series will give a brief introduction to Elixir for developers who already know Ruby. But before we get into the details of Elixir's syntax, it's worth reviewing some of the higher-level differences between the two languages. In particular, here are the three most important differences between Elixir and Ruby:

  • Elixir is functional. Unlike Ruby, it's not object-oriented and has no concept of "classes" or inheritance.
  • Everything in Elixir is immutable.
  • Elixir is compiled while Ruby is interpreted.

Let's look at those three points in more detail…

Elixir is Functional, not Object-Oriented

The most important difference between Elixir and Ruby is that Elixir is not object-oriented.

Elixir has no classes. You don't initialize "objects" with instance methods. Instead, you write functions that are grouped into modules. For this reason, Elixir code can be structured very differently to the equivalent Ruby.

But to be clear, Elixir still has types, like strings, integers, floats, and lists. It's just that these datatypes don't work in the object-oriented way that you're used to.

To illustrate: in Ruby, strings like "this" are instances of the class String. That class has various instance methods like upcase, reverse, or length which you can call on a particular string using .-syntax. Open irb in a terminal and try it:

irb> name = "Pikachu"
irb> name.upcase
"PIKACHU"
irb> name.reverse
"uhcakiP"
irb> name.length
7
Enter fullscreen mode Exit fullscreen mode

In Elixir, String isn’t a class, because Elixir doesn't have classes. Instead, String is a module - a collection of functions that apply to a string. So instead of writing name.upcase you write String.upcase(name).

upcase isn't a method on strings (Elixir doesn't have methods). It's a function that takes a string as its first argument.

Follow the official instructions to install Elixir if you don't have it already. Then open another terminal window and start iex, the Elixir equivalent of irb. Here’s how the String operations we saw above are performed in Elixir:

iex> name = "Pikachu"
iex> String.upcase(name)
"PIKACHU"
iex> String.reverse(name)
"uhcakiP"
iex> String.length(name)
7
Enter fullscreen mode Exit fullscreen mode

(Note that you exit iex by pressing Ctrl + C twice, whereas to exit irb you press Ctrl + D once.)

Elixir has modules like Integer, List and Float, each containing functions to apply to their respective types:

iex> List.first([1,2,3])
1
iex> Float.floor(3.14)
3.0
iex> Integer.digits(90210)
[9, 0, 2, 1, 0]
Enter fullscreen mode Exit fullscreen mode

If a function takes more than one argument, normally the first argument has the same type as the module. See for example these two ways of testing whether a regex matches a string (regexes in Elixir are written with ~r/…/):

iex> Regex.match?(~r/se[0-9]en/, "se7en")
true
iex> String.match?("se7en", ~r/se[0-9]en/)
true
Enter fullscreen mode Exit fullscreen mode

Both these functions test whether a string matches a regex, returning a boolean. The only difference is the order of the arguments: Regex.match? takes the regex first, String.match? takes the string first. That's the general rule: the name of the module tells you the type of the first argument.

To a Ruby developer, the functional style might feel weird at first. You'll find yourself reaching for instance methods that don't exist, and being initially unsure how to structure something in Elixir when you know how you'd write it in Ruby. But with time the functional style will start to feel natural, and you'll see how it leads to clear, elegant and maintainable code.

Immutability vs Mutability

The next major difference between Elixir and Ruby is that in Elixir, everything is immutable.

In Ruby it's usually possible to mutate objects, which is just a fancy way of saying "change them". For example, the following Ruby creates a string then mutates it in-place:

str = "hello"
str.capitalize!
str << "!"
puts str
# Hello!
Enter fullscreen mode Exit fullscreen mode

The variable str still points to the same memory location, but the contents of that memory have mutated from "hello" to "Hello!".

The disadvantage of mutability is that it makes your code unpredictable. When you pass a string to a function, you don't necessarily know what happens to it. The function could mutate your string without you realising, causing unexpected behavior further down the line.

Not everything in Ruby is like this. For example, symbols like :foobar are immutable. They can't be changed after they've been created. The only way to "capitalize" :foobar is to create an entirely new symbol, :FOOBAR, at a new memory location.

In Elixir, everything behaves like a Ruby symbol. There's no equivalent of capitalize! on a Elixir string because Elixir can't mutate the string in place. You can only use String.capitalize (which we saw above), which returns an entirely new string, leaving the original one unchanged.

We'll see in the lessons ahead how Elixir's immutability makes for predictable, reliable and maintainable code.

Compiled vs Interpreted

Elixir is a compiled language. Elixir files end in .ex, and get compiled to BEAM files with extension .beam. These compiled files are ultimately what gets run when you execute your code.

Try it yourself by creating a file called math.ex. (Note that Elixir's IO.puts is the equivalent of Ruby's puts):

defmodule Math do
  def add(a, b) do
    a + b
  end
end

result = Math.add(2, 2)
IO.puts(result)
Enter fullscreen mode Exit fullscreen mode

Compile and run the code with elixirc:

$ elixirc math.ex
4
Enter fullscreen mode Exit fullscreen mode

This created a compiled file called Elixir.Math.beam in the current directory. .beam files are run on BEAM, the same virtual machine that's used to run Erlang, a veteran functional programming language that's been around since the ’80s.

(If you’re familiar with the Java ecosystem: Elixir and Erlang both run on BEAM in the same way that Java, Scala and Kotlin all run on the JVM.)

.beam files are binary files that you shouldn't edit directly. Make sure to exclude them from your version control system.

BEAM, AKA the Erlang VM, is widely used in telecoms and is renowned for its speed, scalability and fault tolerance - all good qualities for a modern web app!

Elixir might look like Ruby on the surface, but its underlying design is heavily based on Erlang. In fact it wouldn't be too far off to describe Elixir as "Erlang with Ruby syntax".

As well as the elixirc command, there's also elixir, which compiles and runs your code but doesn't save a .beam file to disk. Try creating a one-line file called hello_world.exs. (Note the different ending, .exs instead of .ex. The extra s stands for "script".)

IO.puts("Hello, world!")
Enter fullscreen mode Exit fullscreen mode

Now run it using elixir instead of elixirc:

$ elixir hello_world.exs
Hello, world!
Enter fullscreen mode Exit fullscreen mode

This time around, no .beam file was created. elixir compiles your code just like elixirc, but it drops the compiled output once its done with it and doesn't save it. .exs files are useful for scripting or testing.

By convention we use the .ex extension for files we intend to compile to disk with elixirc, and .exs for files we intend to run as "one-off" scripts we intend to run with elixir. This isn't mandatory - you can use elixir on an .ex file or elixirc on an .exs file if you want. But it's good to stick to convention.

In practice you'll rarely need to run the elixir or elixirc commands directly when working on an Elixir (or Phoenix) project. Most of the time you'll run or compile Elixir code using mix, Elixir's build tool, as we'll see throughout the course. (For more on what happens when you run elixir or elixirc, see this article, but it's not important for now if you're a beginner.)

With that out of the way, we can start talking about syntax. What does Elixir code actually look like? We'll see in the next extract.

Top comments (3)

Collapse
 
smathy profile image
Jason King

the contents of that memory have mutated from "hello" to "HELLO!"

You meant "Hello!"

Collapse
 
arrowsmith profile image
George Arrowsmith

You are correct! Thanks - have fixed the typo.

Collapse
 
jaynwabueze profile image
Jay Codes

Welcome to Dev community