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
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
(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]
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
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!
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)
Compile and run the code with elixirc
:
$ elixirc math.ex
4
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!")
Now run it using elixir
instead of elixirc
:
$ elixir hello_world.exs
Hello, world!
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)
You meant
"Hello!"
You are correct! Thanks - have fixed the typo.
Welcome to Dev community