Elixir syntax for Ruby developers: a quick introduction

Learn Phoenix LiveView is the comprehensive tutorial that teaches you everything you need to build a complex, realistic, fully-featured web app with Phoenix LiveView. Click here to learn more!

In the last lesson we covered the three most important differences between Ruby and Elixir. Specifically:

  • Elixir is functional, not object-oriented.
  • Everything in Elixir is immutable.
  • Elixir is compiled.

With these concepts in mind, it’s time to look at some actual Elixir code. In this lesson we’ll take a high-level overview of Elixir’s basic syntax, understanding where it’s similar to Ruby and where it’s different. My aim is to give Ruby developers the fastest possible introduction to Elixir.

(If you want to try these examples for yourself, remember that you can open an interactive Elixir console using iex, the Elixir equivalent of irb.)

Variables

Like in Ruby, variable names in Elixir conventionally use snake_case:

some_number = 1

Unlike Ruby, variable names can end with ? or !:

valid? = true
password! = "foobar"

Strings

Strings in Elixir are written with "double quotes". Unlike in Ruby, they can only use double quotes. Single quotes 'like this' create something weird called a charlist which you rarely need to use in real life[1].

Concatenate strings with <> (it’s + in Ruby):

iex> "Hello" <> " " <> "there"
"Hello there"

As in Ruby, you can interpolate data into a string using #{}:

iex> name = "James Bond"
iex> "The name's #{name}"
"The name's James Bond"
iex> "two plus two = #{2 + 2}"
"two plus two = 4"

Comments

Elixir comments, like Ruby comments, start with #. There’s no “multiline comment” in Elixir.

# This is a comment and won't be executed!

Booleans and Nil

The boolean values in Elixir are just like in Ruby: true and false.

There’s also a null type, which again is the same as Ruby: nil.

Like in Ruby, the only two “falsey” values in Elixir are nil and false. So if you’re testing for truthiness in, say, an if condition, everything that doesn’t evaluate to nil or false will be considered true.

The boolean operators &&, || and ! all work the same way as in Ruby.

Learn Phoenix fast

Phoenix on Rails is a 72-lesson tutorial on web development with Elixir, Phoenix and LiveView, for programmers who already know Ruby on Rails.

Get part 1 for free:

Conditionals

if and else look like Ruby, except you need to write a do after the if:

if age >= 18 do
  "Adult"
else
  "Minor"
end

Like Ruby, Elixir has an unless statement as well as if:

unless age >= 18 do
  "Minor"
else
  "Adult"
end

Any variables you define within the body of an if will be scoped within that if. They won’t override a previous definition of that variable:

iex> x = "Something"
iex> if true do
...>   x = "Something else"
...> end

# What's the value of x now?

iex> x
"Something"

Note that this is different to Ruby:

irb> x = "Something"
irb> if true
...>   x = "Something else"
...> end

irb> x
"Something else"

If you want to set a variable based on an if condition in Elixir, you can return a value from the if expression itself:

iex> x = if true do
...>   "Something"
...> else
...>   "Something else"
...> end

iex> x
"Something"

Elixir has no elsif. If you need multiple conditions, you could write an ugly bunch of nested ifs:

if age >= 65 do
  "OAP"
else
  if age >= 18 do
    "Adult"
  else
    if age >= 13 do
      "Teenager"
    else
      "Child"
    end
  end
end

But there’s a better way: use cond:

cond do
  age >= 65 -> "OAP"
  age >= 18 -> "Adult"
  age >= 13 -> "Teenager"
end

cond checks each condition in turn until it finds one that evaluates to something truthy (that is, anything except false or nil).

If none of the conditions are true - for example if the above code is run when age == 12 - then cond will raise an error. If you want a “fallback” clause that matches when nothing else does, you can simply use true as a condition, since this will always be truthy!

age = 12
category = cond do
  age >= 65 -> "OAP"
  age >= 18 -> "Adult"
  age >= 13 -> "Teenager"
  true -> "Child"
end
IO.puts(category)
# "Child"

for

Loop over an enumerable value with for:

for number <- [1, 2, 3, 4] do
  IO.puts(number)
end

# 1
# 2
# 3
# 4

for loops are expressions that return a value. (In fact, everything in Elixir is an expression, meaning every line returns a computed value.) So you can assign the result of for to a variable:

squares = for n <- [1,2,3,4] do
  n * n
end

IO.puts squares
# [1,4,9,16]

for loops are called comprehensions and have many advanced features which you can read about in the official docs.

Elixir has no while keyword.

Numbers

Mathematical stuff mostly works the way you’d expect:

iex> 2 + 2
4
iex> 2 - 5
-3
iex> 3 * 4
12

In Ruby / performs integer division, but in Elixir it performs normal division:

# Ruby:
5 / 2
# => 2

# Elixir:
5 / 2
# => 2.5

To get integer division in Elixir, use the div function:

iex> div(5, 2)
2

In Ruby, % is the modulo operator - that is, a % b means “the remainder of a divided by b.” In Elixir, use rem:

# Ruby:
6 % 4
# => 2

# Elixir:
rem(6, 4)
# => 2

Use ** for exponentiation:

iex> 2 ** 4
16

floor, ceil, round, abs, max and min all do what you’d expect:

iex> floor(5.5)
5
iex> ceil(5.5)
6
iex> round(5.5)
6
iex> round(5.4999)
5
iex> abs(-5)
5
iex> max(2, 3)
3
iex> min(2, 3)
2

Equality

Use == or === - or their opposites, != and !== - to test equality.

== means “equals” while === means “exactly equals”. The difference is best explained by example:

2 ==  2   # true
2 === 2   # true
2 ==  2.0 # true
2 === 2.0 # false

Functions

Elixir functions are written with def - but unlike Ruby, you also need to write a do:

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

We refer to functions with the format name/n, where name is the function’s name and n is the number of arguments it takes. So the above function is called add/2.

Like in Ruby, functions always return the value computed by their last line. So add/2 returns the value of a + b.

Unlike Ruby, Elixir doesn’t have a return statement. You can’t “exit early” from an Elixir function - the only way to return something is to make it be the value of the function’s final expression.

Use \\ to define default arguments:

def choose_color(color \\ "black") do
  IO.puts("You chose #{color}")
end

choose_color("red")
# "You chose red"
choose_color()
# "You chose black"

Like in Ruby, parentheses are optional when calling a function:

choose_color "red"
# "You chose red"
choose_color
# "You chose black"

Also like Ruby, function names can end with a ? or !. By convention, functions that end in ? return a bool:

String.contains?("England", "gland")
# => true

And functions that end an ! raise an exception in their error cases. For example, File.read and File.read! both attempt to read a file from disk, but File.read returns an error message if the file can’t be found, while File.read! raises an exception:

File.read("file_that_exists.txt")
# => {:ok, "this is the file's contents"}
File.read("file_that_doesnt_exist.txt")
# => {:error, :enoent}

File.read!("file_that_exists.txt")
# => "this is the file's contents"
File.read!("file_that_doesnt_exist.txt")
# ** (File.Error) could not read file file_that_doesnt_exist.txt: no such file or directory

As in Ruby, these “rules” around ? and ! are just conventions. There’s nothing to stop you from creating a function whose name ends in ? but that doesn’t return a bool, or whose name ends in ! but that doesn’t raise any exceptions. But it’s recommended you stick to convention.

Anonymous functions

Anonymous functions are, well, anonymous - they have no name. Create one using the fn and end keywords. They can have any number of parameters, and the parameters are separated from the function body by ->. (Note: you don’t need a do.)

fn x, y ->
  x + y
end

You can assign an anonymous function to a variable. To call it, you must write a . before the opening parenthesis:

iex> sum = fn x, y -> x + y end
iex> sum.(1, 2)
3

A common use for anonymous functions is to pass them as arguments to other functions:

iex> Enum.map([1, 2, 3, 4], fn n -> n ** 2 end)
[1, 4, 9, 16]

iex> Enum.reduce([1, 2, 3, 4], fn x, acc -> x + acc end)
10

There’s a shorthand syntax for creating anonymous functions:

# This:
sum1 = fn x, y -> x + y end

# Is equivalent to this:
sum2 = &(&1 + &2)
sum2.(3,4)
# => 7

# The brackets are optional:
sum3 = & &1 + &2
sum3.(3,4)
# => 7

When using this syntax, &1, &2, &3, etc. are short for the first, second, third etc. arguments to the function.

iex> Enum.map([1, 2, 3, 4], &(&1 ** 2))
[1, 4, 9, 16]

iex> Enum.reduce([1, 2, 3, 4], &(&1 + &2))
10

Regexes

Regex literals in Elixir are written between ~r/ and /. That is, they ~r/look like this/.

iex> Regex.match?(~r/se[0-9]en/, "se7en")
true
iex> Regex.match?(~r/se[0-9]en/, "seven")
false

You can use other delimiters, such as ~r() or ~r'':

# These are all equivalent:
~r/se[0-9]en/
~r(se[0-9]en)
~r'se[0-9]en'

For more on this, see the documentation on sigils.

inspect

As well as puts, Ruby provides a method p, which prints a coder-friendly representation of the object that’s useful when debugging. Sometimes puts and p print the same thing as each other, but they often differ:

puts :symbol
# symbol
p :symbol
# :symbol

puts nil # prints nothing
#
p nil
# nil

puts [1, 2] # prints each item on a separate line
# 1
# 2
p [1, 2]
# [1, 2]

The Elixir equivalent of p is IO.inspect:

IO.puts "string"
# string
IO.inspect "string"
# "string"

There’s also inspect (no IO), which returns the value that would be printed by IO.inspect, but doesn’t print it.

Exceptions

As in Ruby, you can raise an exception with raise:

iex> raise "something's wrong!"
** (RuntimeError) something's wrong!

try/rescue is the equivalent of Ruby’s begin/rescue:

iex> try do
...>   raise "something's wrong"
...> rescue
...>   e in RuntimeError -> e.message
...> end
"something's wrong"

Elixir’s after keyword works like ensure in Ruby. else works the same in both languages.

try do
  # code that might raise an exception
rescue
  # code that handles the error
else
  # code that only executes if there was no error
after
  # code that's always executed, whether or not there was an error
end

You can also throw a value and catch it:

iex> try do
...>   throw 1
...> catch
...>   x -> "#{x} was caught"
...> end
"1 was caught"

There’s almost always a better, more readable way to solve a problem than with throw and catch. Don’t use them unless you truly have no other choice!

As in Ruby, if you want to catch all errors raised by a function, you can drop the try and use the rescue keyword by itself:

iex> defmodule Foo do
...>   def bar do
...>     raise "something's wrong"
...>   rescue
...>     RuntimeError -> "rescued error"
...>   end
...> end
iex> Foo.bar
"rescued error"

Generally, you shouldn’t use raise, try, rescue etc. very often in Elixir. There’s usually a better way to structure your code using concepts from functional programming - as we’ll see in this course.

For more on exceptions, try, catch and rescue, see the docs.

Recap

Don’t feel like you need to memorize all of the above before continuing. You can use this lesson as a reference; come back to it in future if you’re not sure about anything.

If you’re used to Ruby then most of the basic Elixir syntax should feel familiar. Just remember that:

  • Strings must use double quotes, not single quotes.
  • Functions and if/unless must have a do on their opening lines.
  • There are no return or elsif keywords.

Those are the three differences most likely to trip up a Ruby developer. If you remember anything for now, start with those three points.

There’s still a lot to learn, including Elixir’s more advanced types like atoms, maps, tuples and structs. We also haven’t covered two of the most useful and powerful features in Elixir: pattern matching and the pipe operator. We’ll get to that in future lessons.