Elixir syntax for Ruby developers: a quick introduction
Tags: elixir, ruby
Published: 2023-09-05 06:09:03

**This article is part of a series of extracts from [*Phoenix on Rails*](https://phoenixonrails.com), 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](https://phoenixonrails.com)!**

In [the last lesson](https://dev.to/arrowsmith/elixir-for-ruby-developers-the-three-most-important-differences-3dkb) 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`:

```elixir
some_number = 1
```

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

```elixir
valid? = true
password! = "foobar"
```

## Strings

<p x-data="{tooltip: 'The Elixir docs say: “In practice, you will not come across [charlists] often, only in specific scenarios such as interfacing with older Erlang libraries that do not accept binaries as arguments.“'}">
Strings in Elixir are written with <code class="inline">"double quotes"</code>.  Unlike in Ruby, they can <b>only</b> use double quotes. Single quotes <code class="inline">'like this'</code> create something weird called a <a href="https://elixir-lang.org/getting-started/binaries-strings-and-char-lists.html#charlists">charlist</a> which you rarely need to use in real life.
</p>

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

```elixir
iex> "Hello" <> " " <> "there"
"Hello there"
```

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

```elixir
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.

```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`:

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

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

```elixir
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:

```elixir
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:

```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:

```elixir
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:

```elixir
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`:

```elixir
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!

```elixir
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`:

```elixir
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:

```elixir
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](https://elixir-lang.org/getting-started/comprehensions.html).

Elixir has no `while` keyword.

## Numbers

Mathematical stuff mostly works the way you'd expect:

```elixir
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
# Ruby:
5 / 2
# => 2

# Elixir:
5 / 2
# => 2.5
```

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

```elixir
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
# Ruby:
6 % 4
# => 2

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

Use `**` for exponentiation:

```elixir
iex> 2 ** 4
16
```

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

```elixir
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:

```ruby
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`:

```elixir
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:

```elixir
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:

```elixir
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:

```elixir
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:

```elixir
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`.)

```elixir
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:

```elixir
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:

```elixir
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:

```elixir
# 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.

```elixir
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/`.

```elixir
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'`…`'`:

```elixir
# 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](https://elixir-lang.org/getting-started/sigils.html).

## 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:

```ruby
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`:

```elixir
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`:

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

`try`/`rescue` is the equivalent of Ruby's `begin`/`rescue`:

```elixir
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.

```elixir
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:

```elixir
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:

```elixir
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](https://elixir-lang.org/getting-started/try-catch-and-rescue.html).

## 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.