Elixir leverages the Erlang VM, known for running low-latency, distributed and fault-tolerant systems, while also being successfully used in web development and the embedded software domain. It’s functional paradigm and immutable data structures make it a great choice for building scalable and maintainable applications.

Few things I like about Elixir are

  • The syntax is very clean and easy to read.
  • The built-in support for concurrency and fault-tolerance.
  • The ability to write highly concurrent and distributed systems with ease.

Few things I dislike about Elixir are

  • The lack of type system

Variables

In Elixir, variables are declared using the = operator. Variables are immutable, meaning that once a value is assigned to a variable, it cannot be changed. Elixir is a dynamically typed language, so the type of a variable is determined at runtime.

Syntax and Example:

a = 10 # Variable declaration
b, c = 20, 30 # Multiple variables

# Atoms
status = :ok # ok is an atom, a constant with its name as its value

Control Statements

Control statements in Elixir direct the flow of execution. They include if, else, case, and looping constructs like for.

Syntax and Example:

# If statement
if x > 0 do
    IO.puts("x is positive")
end

# Case statement
case x do
    1 -> IO.puts("x is 1")
    2 -> IO.puts("x is 2")
    _ -> IO.puts("x is neither 1 nor 2")
end

# For loop
for i <- 1..10 do
    IO.puts(i)
end

Functions

Elixir is a functional language, and functions are first-class citizens. Functions can be defined using the def keyword.

Syntax and Example:

# Function definition
defmodule Math do
    def add(a, b) do
        a + b
    end
end

# Function call
Math.add(10, 20)

# Anonymous functions
add = fn a, b -> a + b end
add.(10, 20)

# Pattern matching
defmodule Math do
    def add(0, b), do: b
    def add(a, 0), do: a
    def add(a, b), do: a + b
end
Math.add(0, 10)

# Higher-order functions
Enum.map([1, 2, 3], fn x -> x * 2 end)

# Pipe operator
defmodule Math do
    def add(a, b), do: a + b
    def double(x), do: x * 2

    def add_and_double(a, b) do
        a
        |> add(b)
        |> double()
    end
end

Data Structure

Elixir has struct to define a data structure. It is a map with a predefined set of keys.

Syntax and Example:

defmodule User do
    defstruct name: "John", age: 27
end

# Create a new struct
user = %User{name: "Jane", age: 30}

Collection Manipulation

Elixir provides a set of functions for working with collections. These functions are available in the Enum and Stream modules.

Syntax and Example:

# List definition
list = [1, 2, 3, 4, 5]

# Iterating over a list
Enum.each(list, fn x -> IO.puts(x) end)

# Mapping over a list
Enum.map(list, fn x -> x * 2 end)

# Filtering a list
Enum.filter(list, fn x -> x > 2 end)

# Reducing a list
Enum.reduce(list, 0, fn x, acc -> x + acc end)

# Stream
Stream.map([1, 2, 3], fn x -> x * 2 end)

# Range
Enum.to_list(1..5)

# Maps
map = %{a: 1, b: 2, c: 3}
map[:a]

# Keyword lists
list = [{:a, 1}, {:b, 2}, {:c, 3}]

# Tuples
tuple = {:ok, "hello"}

Error Handling

Elixir uses the {:ok, result} and {:error, reason} tuples to handle errors. This approach encourages explicit error checking.

Syntax and Example:

# Function returning an error
defmodule Math do
    def sqrt(x) when x >= 0, do: {:ok, :math.sqrt(x)}
    def sqrt(x), do: {:error, "Cannot calculate square root of negative number"}
end

# Pattern matching to handle errors
case Math.sqrt(-16) do
    {:ok, result} -> IO.puts(result)
    {:error, reason} -> IO.puts(reason)
end

# Using the try statement
try do
    Math.sqrt(-16)
rescue
    e in ArgumentError -> IO.puts("Invalid argument: #{e.message}")
end

# Using the with statement
with {:ok, a} <- Math.sqrt(16),
     {:ok, b} <- Math.sqrt(9),
     do: IO.puts(a + b)

# Throw and catch
try do
    throw(:error)
catch
    :error -> IO.puts("Caught error")
end

Concurrency

Elixir provides lightweight processes called tasks that run concurrently. These tasks communicate with each other using message passing.

Syntax and Example:

# Spawn a new task
task = Task.async(fn -> IO.puts("Hello, World!") end)

# Wait for the task to complete
Task.await(task)

# Send and receive messages
send(self(), {:hello, "World"})

receive do
    {:hello, name} -> IO.puts("Hello, #{name}!")
end

# Spawn multiple tasks
tasks = for i <- 1..5, do: Task.async(fn -> IO.puts(i) end)
Enum.each(tasks, &Task.await/1)

Ecosystem

Installation

Elixir can be installed on various platforms using package managers like apt, yum, or brew.

# Install Elixir using apt
sudo apt-get install elixir

# Install Elixir using brew
brew install elixir

Hello World

IO.puts("Hello, World!")

Build and Run

# Run the program
elixir hello.exs

Package Management

Elixir uses mix as a build tool and package manager. It is used to create, compile, and test Elixir projects.

# Create a new Elixir project
mix new project_name

# Compile the project
mix compile

# Run tests
mix test

# Install dependencies
mix deps.get
  • Phoenix: A web development framework for Elixir.
  • Ecto: A database wrapper and query generator for Elixir.
  • Absinthe: A GraphQL toolkit for Elixir.
  • Broadway: A concurrent and multi-stage data ingestion and data processing with Elixir.
  • Nerves: A framework for building embedded software in Elixir.