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
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.
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.
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 if
s:
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 ado
on their opening lines. - There are no
return
orelsif
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)