Elixir Syntax Guide - Understand Variables, Operators, and Pattern Matching

Master Elixir syntax with this comprehensive guide. Learn about variable declaration, operators, pattern matching, comments, and reserved words for efficient Elixir programming.

Elixir Syntax Fundamentals

Understanding Elixir Syntax

This guide covers the fundamental syntax elements of the Elixir programming language, essential for any developer looking to build robust and scalable applications.

Variables in Elixir

Variables in Elixir are declared and initialized upon their first use. They follow a specific naming convention: starting with a lowercase letter, followed by zero or more letters, numbers, or underscores, and optionally ending with an exclamation mark or a question mark.

Elixir variables are immutable by default, but they can be reassigned. They can hold any data structure.

> something = :anything
> something = ["a", "list", "of", "strings"]
> _yeeHaw1234! = %{:a => :b}

Operators in Elixir

Elixir supports a variety of operators for different purposes:

Standard Infix Operators

  • Equality: ==, !=
  • Strict Equality: ===, !== (do not coerce Floats to Integers, e.g., 1 === 1.0 #false)
  • Comparison: >, <, >=, <=
  • Logic (short-circuiting): && and ||
  • Boolean-only Logic (short-circuiting): and and or (only the left side must be boolean)
  • Arithmetic: +, -, *, /

Standard Prefix Operators

  • Negation (any type): ! (e.g., !1 == false)
  • Negation (boolean only): not (e.g., not is_atom(5) == true)

Match Operator =

The = operator performs a Pattern Match. It attempts to match the structure on the left with the data structure on the right.

Pin Operator ^

The ^ operator is used to pin the value of a variable on the left side of a Pattern Match, ensuring its value is not changed during the match.

a = "thirty hams"
{b, ^a} = {:i_need, "thirty hams"}            # `b` is set to `:i_need`
{^a, {^a}} = {"thirty hams", {"thirty hams"}} # nothing is set, but the match succeeds

Pipe Operator |>

The |> operator takes the result of a statement on its left and passes it as the first argument to the function on its right. The statement on the left can span multiple lines.

> [1,2,3] |> hd |> Integer.to_string |> IO.inspect # "1"
# ⇣ doesn't work in iex
hd([1,2,3])
|> Integer.to_string
|> IO.inspect  # "1"

String Match Operator =~

The =~ operator checks if a string on the left contains a substring or matches a regular expression on the right.

> "abcd" =~ ~r/c(d)/ # true
> "abcd" =~ ~r/e/    # false
> "abcd" =~ "bc"     # true
> "abcd" =~ "ad"     # false

Codepoint Operator ?

The ? operator returns the UTF-8 codepoint of the character immediately to its right. It accepts single characters and Escape Sequences.

> ?a   # 97
> ?♫   # 9835
> ?\s  # 32
> ??   # 63
> [?♀, ?!] == '♀!'  # true

Capture Operator &

TODO: Add explanation for the capture operator.

Ternary Operator (Simulated)

Elixir does not have a built-in ternary operator. The same functionality can be achieved using the if macro.

> a = if true, do: "True!", else: "False!"
> a == "True!"  # true

in Operator

The in operator checks if an enumerable on the right contains the data structure on the left. The right-hand side must implement the Enumerable Protocol.

> :b in [:a, :b, :c] # true
> [:c] in [1,3,[:c]] # true
> :ok in {:ok} # ERROR: protocol Enumerable not implemented for {:ok}

Comments

Comments in Elixir start with a hash symbol (#) and continue to the end of the line.

Semicolons

Semicolons can be used to terminate statements but are rarely used in practice. Their primary use is to place multiple statements on the same line (e.g., a = 1; b = 2), which is considered poor style. Prefer placing statements on separate lines.

do and end Blocks

Blocks of code passed to macros typically start with do and end with end.

if true do
  "True!"
end

if true do "True!" end

# inside a module
def somefunc() do
  IO.puts "multi line"
end

if true do
  "True!"
else
  "False!"
end

You can use syntactic sugar for single-line blocks:

#      ⇣   ⇣         ⇣ no end keyword
if true, do: "True!"
#      ⇣   ⇣       ⇣     ⇣          ⇣ no end keyword
if true, do: "True", else: "False!"
# inside a module
#             ⇣   ⇣                              ⇣ no end keyword
def someFunc(), do: IO.puts "look ma, one line!"

This is syntactic sugar for:

if(true, [{:do, "True!"}, {:else, "False!"}])
def(someFunc(), [{:do, IO.puts "look ma, one line!"}])

Pattern Matching

Pattern matching is a core feature in Elixir, allowing you to destructure data and bind variables based on the structure of the data.

#     ┌Left       ┌Right
# ┌───┴───┐   ┌───┴──────┐
  {:one, x} = {:one, :two}
#        ┌Right
#    ┌───┴──────┐  
case {:one, :two} do
#     ┌Left
# ┌───┴───┐
  {:one, x} -> IO.puts x
# ┌Left
  _         -> IO.puts "no other match"
end

The right side is a data structure. The left side attempts to match itself to the data structure on the right and bind any variables to substructures.

A simple match with a lone variable on the left will match anything:

# in these examples `x` will be set to whatever is on the right
x = 1
x = [1,2,3,4]
x = {:any, "structure", %{:whatso => :ever}}

You can place variables inside a structure to capture substructures:

# `x` gets set to only the `substructure` it matches
{:one, x} = {:one, :two} # `x` is set to `:two` 
[1,2,n,4] = [1,2,3,4]    # `n` is set to `3`
[:one, p] = [:one, {:apple, :orange}] # `p` is set to `{:apple, :orange}`

The special _ variable matches anything but discards the value, indicating you don't care about it.

# in all of these examples, `x` gets set to `:two`
{_, x} = {:one, :two}
{_, x} = {:three, :two}
[_,_,x,_] = [1,{2},:two,3]

If you place a variable on the right, its value is used in the match:

#                          ┌Same as writing {"twenty hams"}
a = {"twenty hams"}        ⇣
{:i_have, {b}} = {:i_have, a} # `b` is set to "twenty hams"

The ^ operator allows you to use the value of a variable in the left side of a pattern match.

a = "thirty hams"
{b, ^a} = {:i_need, "thirty hams"}            # `b` is set to `:i_need`
{^a, {^a}} = {"thirty hams", {"thirty hams"}} # nothing is set, but the match succeeds

Maps Pattern Matching

Individual keys can be matched in Maps:

nola = %{ name: "New Orleans", founded: 1718 }
%{name: city_name} = nola # city_name now equals "New Orleans"
%{name: _, founded: city_founded} = nola # Map must have both a name and a founded key

You can use the pin operator (^) to match on variables:

field = "founded"
%{^field: city_founded} = nola # city_founded now equals 1718

Binaries Pattern Matching

Binaries can be deconstructed using size, unit, and type specifiers.

<< size::8, rest::binary>> = <<3,0,25,1,1,2,1,6,4,3>>
<< data::size(size)-unit(16)-binary, rest::binary>> = rest
# TODO: Add more examples for binary matching.

Ranges Pattern Matching

Ranges can be pattern matched if both their values are integers.

min..max = 20..5000
min == 20    # true
max == 5000  # true
min..max == 1..10.0 # This would result in an Argument Error

Reserved Words

The following words are reserved in Elixir and cannot be used as identifiers for variables, modules, or function names:

nil, true, false, __MODULE__,__FILE__,__DIR__,__ENV__,__CALLER__

For more in-depth information on Elixir's syntax and features, refer to the official Elixir documentation.