No history yet

Elixir Basics

Welcome to Elixir

Elixir is a functional programming language built on the Erlang virtual machine. It was created by José Valim, a well-known Ruby developer, who wanted a language with the productivity of Ruby but the power to build scalable, fault-tolerant systems. Elixir gives you the best of both worlds.

At its core, Elixir follows a functional programming paradigm. If you're used to object-oriented languages like Java or Python, this might feel a little different. In functional programming, data is immutable, meaning it can't be changed once it's created. Instead of modifying data, you create new data through transformations.

Think of it like an assembly line. Each station (a function) takes an item, performs a specific task, and passes a new, altered item to the next station. The original item is never changed.

The Building Blocks

Let's start with the basic data types. Like most languages, Elixir has numbers, strings, and booleans, but it also has a few unique types.

iex> 1          # integer
iex> 1.0        # float
iex> 10 / 2     # 5.0 (division always produces a float)
iex> div(10, 2) # 5   (for integer division)
iex> rem(10, 3) # 1   (for the remainder)

Atoms are constants where their name is their value. They're often used to represent status, like :ok or :error. Booleans are just special atoms: true is the same as :true and false is :false.

Strings in Elixir are enclosed in double quotes and support interpolation, which is a handy way to embed expressions inside a string.

iex> name = "Alice"
iex> "Hello, #{name}" # "Hello, Alice"

Elixir also has three main collection types: lists, tuples, and maps.

  • Lists are for ordered collections. They are implemented as linked lists, which makes it very fast to add items to the front.
  • Tuples are also ordered collections, but they are stored contiguously in memory, making them fast to access by index. They're often used to return multiple values from a function.
  • Maps are the go-to for key-value pairs.
# List (square brackets)
iex> [1, 2, 3]

# Tuple (curly braces)
iex> {:ok, "Success!"}

# Map (% and curly braces)
iex> %{:name => "Bob", :age => 30}

Pattern Matching

One of Elixir's most powerful features is pattern matching. It's not just for checking equality; it's a way to deconstruct data. The = symbol in Elixir is the match operator, not an assignment operator.

Pattern matching is like checking if a key fits a lock. If it fits, the lock opens and you can access what's inside. If not, it fails.

# This matches, and binds the value 1 to x
iex> x = 1

# This also matches, because x (which is 1) matches 1
iex> 1 = x

# This will fail with a MatchError, because 2 does not match x (1)
iex> 2 = x

# Deconstructing a tuple
iex> {:ok, result} = {:ok, "User found"}
iex> result # "User found"

# Deconstructing a list (the 'head' and 'tail')
iex> [head | tail] = [1, 2, 3]
iex> head # 1
iex> tail # [2, 3]

Pattern matching is used everywhere in Elixir, especially in control structures like the case statement. It lets you execute different code blocks based on the shape of the data.

def check_status(status) do
  case status do
    :ok -> 
      "Everything is fine."
    {:error, message} -> 
      "An error occurred: #{message}"
    _ -> 
      "Unknown status."
  end
end

check_status({:error, "Network timeout"})
# "An error occurred: Network timeout"

In the example above, the _ is a wildcard that matches anything. It's useful as a catch-all for patterns you don't handle explicitly.

Functions and Recursion

In Elixir, code is organized into modules. A module is a collection of functions. We define them with defmodule and def.

defmodule Math do
  # A public function with an arity of 2
  def add(a, b) do
    a + b
  end

  # A private function
  defp secret_calculation do
    42
  end
end

# Call the function
Math.add(5, 10) # 15

Functions are identified by their name and their arity (the number of arguments they take). So, add/2 is a different function from a potential add/3. Functions defined with defp are private and can only be called from within the same module.

Since data is immutable, we can't use traditional loops that modify a counter variable. Instead, functional languages use recursion. A recursive function is one that calls itself until it reaches a base case.

Let's write a function to sum a list of numbers recursively. We'll use pattern matching in the function definition to handle the base case (an empty list) and the recursive step.

defmodule MyList do
  # Base case: the sum of an empty list is 0.
  def sum([]), do: 0

  # Recursive step: for a non-empty list,
  # the sum is the head plus the sum of the tail.
  def sum([head | tail]) do
    head + sum(tail)
  end
end

MyList.sum([1, 2, 3]) # 6

Elixir also supports higher-order functions, which are functions that can take other functions as arguments. The built-in Enum module is full of them. For instance, Enum.map/2 takes a list and a function, and applies the function to every element in the list, returning a new list.

iex> Enum.map([1, 2, 3], fn x -> x * 2 end)
[2, 4, 6]

Here, fn x -> x * 2 end is an anonymous function. These are handy for short, one-off operations. Combining these tools—modules, pattern matching, recursion, and higher-order functions—is the key to writing idiomatic Elixir code.

Time to test your knowledge on these core concepts.

Quiz Questions 1/6

What is a core principle of Elixir's design, stemming from its functional programming paradigm?

Quiz Questions 2/6

In Elixir, what is an atom?

These are the fundamental building blocks of Elixir. With a solid grasp of these ideas, you're ready to explore how Elixir builds powerful, concurrent applications.