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
andor
(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.