DEV Community

George Arrowsmith
George Arrowsmith

Posted on

Elixir syntax for Ruby developers: a quick introduction

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!

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
Enter fullscreen mode Exit fullscreen mode

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

valid? = true
password! = "foobar"
Enter fullscreen mode Exit fullscreen mode

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.

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

iex> "Hello" <> " " <> "there"
"Hello there"
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

Comments

Elixir comments, like Ruby comments, start with #. There's no "multiline comment" in Elixir.

# This is a comment and won't be executed!
Enter fullscreen mode Exit fullscreen mode

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.

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
Enter fullscreen mode Exit fullscreen mode

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

unless age >= 18 do
  "Minor"
else
  "Adult"
end
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

Note that this is different to Ruby:

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

irb> x
"Something else"
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

But there's a better way: use cond:

cond do
  age >= 65 -> "OAP"
  age >= 18 -> "Adult"
  age >= 13 -> "Teenager"
end
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

for

Loop over an enumerable value with for:

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

# 1
# 2
# 3
# 4
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

# Ruby:
5 / 2
# => 2

# Elixir:
5 / 2
# => 2.5
Enter fullscreen mode Exit fullscreen mode

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

iex> div(5, 2)
2
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Use ** for exponentiation:

iex> 2 ** 4
16
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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

choose_color "red"
# "You chose red"
choose_color
# "You chose black"
Enter fullscreen mode Exit fullscreen mode

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

String.contains?("England", "gland")
# => true
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

The Elixir equivalent of p is IO.inspect:

IO.puts "string"
# string
IO.inspect "string"
# "string"
Enter fullscreen mode Exit fullscreen mode

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!
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

You can also throw a value and catch it:

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

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"
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)