Proč Elixír v roce 2026

Elixir není „jen další funkční jazyk“. Je postaven na Erlangův BEAM Virtual Machine, systém navržený v 80. letech 20. století společností Ericsson spravovat miliony souběžných telefonních spojení s 99,9999999% dostupností. Elixir přináší na tomto základě moderní syntaxi, Mix/Hex toolchain a Phoenix Framework — jeden z nejvýkonnějších webových frameworků zdokumentováno v oboru.

Funkční paradigma Elixíru není estetickou volbou: je co umožňuje odolnost proti chybám a masivní souběžnost BEAM. Chcete-li porozumět OTP a GenServeru (pozdější články), musíte nejprve zvládnout tyto základy.

Co se naučíte

  • Instalace a nastavení pomocí Mix, IEx (interaktivní REPL)
  • Základní typy: atom, n-tice, seznam, mapa, seznam klíčových slov
  • Shoda vzorů: Nejvýkonnější funkce Elixíru
  • Operátor potrubí |>: Čitelná kompozice funkcí
  • Neměnnost: Protože nemůžete změnit proměnnou
  • Funkce: pojmenované, anonymní funkce a funkce vyššího řádu
  • Moduly: Jak organizovat kód v Elixir

Instalace a nastavení

# Installazione su Linux/Mac (via asdf, raccomandato)
asdf plugin add erlang
asdf plugin add elixir

asdf install erlang 26.2.5
asdf install elixir 1.16.3

asdf global erlang 26.2.5
asdf global elixir 1.16.3

# Verifica
elixir --version
# Erlang/OTP 26 [erts-14.2.5] [...]
# Elixir 1.16.3 (compiled with Erlang/OTP 26)

# Crea un nuovo progetto
mix new my_app
cd my_app

# Avvia il REPL interattivo
iex -S mix

# Struttura progetto generata
# my_app/
# ├── lib/
# │   └── my_app.ex        (modulo principale)
# ├── test/
# │   └── my_app_test.exs  (test)
# ├── mix.exs              (configurazione + dipendenze)
# └── README.md

Základní typy a datové struktury

# IEx: esplora i tipi di Elixir

# Atoms: costanti identificate dal loro nome
:ok
:error
:hello
true    # equivale a :true
false   # equivale a :false
nil     # equivale a :nil

# Integers e floats
42
3.14
1_000_000    # underscore per leggibilita'

# Strings: binary UTF-8
"Ciao, mondo!"
"Linea 1\nLinea 2"
"Interpolazione: #{1 + 1}"   # "Interpolazione: 2"

# Atoms binari vs String
:hello == "hello"    # false - tipi diversi!
:hello == :hello     # true - stessa identita'

# Tuple: sequenza fissa di lunghezza nota
{:ok, "valore"}
{:error, :not_found}
{1, 2, 3}

# Pattern comune: tagged tuple per risultati
# {:ok, value} oppure {:error, reason}

# List: linked list (efficiente per head/tail)
[1, 2, 3, 4, 5]
["mario", "luigi", "peach"]
[head | tail] = [1, 2, 3]   # Pattern matching!
# head = 1, tail = [2, 3]

# Map: key-value store
%{name: "Mario", age: 35}      # Atom keys (piu' comune)
%{"name" => "Mario", "age" => 35}  # String keys

# Keyword list: list di tuple {atom, value} (ordinate)
[name: "Mario", age: 35, city: "Milano"]
# Equivale a: [{:name, "Mario"}, {:age, 35}, {:city, "Milano"}]
# Usata per opzioni di funzione

# Range
1..10         # Range inclusivo
1..10//2      # Step 2: [1, 3, 5, 7, 9]

Pattern Matching: The Elixir Core

V Elixíru, = to není úkol: je to operace z zápas. Levá strana je „srovnaná“ s pravou stranou. Pokud je shoda úspěšná, proměnné ve vzoru jsou svázány s odpovídajícími hodnotami. Pokud selže, je zvýšeno a MatchError.

# Pattern matching: le basi

# Binding semplice
x = 42       # x viene legata a 42
42 = x       # OK: x e' 42, il match succeede
# 43 = x     # MatchError: 43 != 42

# Tuple destructuring
{:ok, value} = {:ok, "risultato"}
# value = "risultato"

{:ok, value} = {:error, :not_found}
# MatchError! :ok != :error

# Case expression: match multiplo
result = {:error, :timeout}

case result do
  {:ok, value} ->
    IO.puts("Successo: #{value}")

  {:error, :not_found} ->
    IO.puts("Non trovato")

  {:error, reason} ->
    IO.puts("Errore generico: #{reason}")

  _ ->
    IO.puts("Fallback: qualsiasi altro caso")
end
# Output: "Errore generico: timeout"

# Lista head/tail
[first | rest] = [1, 2, 3, 4, 5]
# first = 1, rest = [2, 3, 4, 5]

[a, b | remaining] = [10, 20, 30, 40]
# a = 10, b = 20, remaining = [30, 40]

# Map matching (partial match: extra keys sono ok)
%{name: name, age: age} = %{name: "Mario", age: 35, city: "Milano"}
# name = "Mario", age = 35

# Pin operator ^: usa il valore corrente, non rebind
existing = 42
^existing = 42   # OK: match con il valore corrente di existing
# ^existing = 43  # MatchError
# Pattern matching in function definitions
defmodule HttpResponse do
  # Diverse implementazioni per pattern diversi
  def handle({:ok, %{status: 200, body: body}}) do
    IO.puts("Success: #{body}")
  end

  def handle({:ok, %{status: 404}}) do
    IO.puts("Not Found")
  end

  def handle({:ok, %{status: status}}) when status >= 500 do
    IO.puts("Server Error: #{status}")
  end

  def handle({:error, reason}) do
    IO.puts("Request failed: #{reason}")
  end
end

# Uso
HttpResponse.handle({:ok, %{status: 200, body: "Hello"}})  # Success: Hello
HttpResponse.handle({:ok, %{status: 404}})                  # Not Found
HttpResponse.handle({:error, :timeout})                     # Request failed: timeout

# Guards (when clause): condizioni aggiuntive
defmodule Validator do
  def validate_age(age) when is_integer(age) and age >= 0 and age <= 150 do
    {:ok, age}
  end

  def validate_age(age) when is_integer(age) do
    {:error, "Age #{age} out of valid range (0-150)"}
  end

  def validate_age(_) do
    {:error, "Age must be an integer"}
  end
end

Pipe Operator: Složení transformací

Provozovatel potrubí |> předá výsledek předchozího výrazu jako první argument další funkce. Transformujte vnořené hovory v čitelné lineární sekvenci — kód vyjadřuje zřetězení transformace v pořadí, v jakém se vyskytují.

# Senza pipe: nidificazione profonda (leggibilita' scarsa)
result = Enum.sum(Enum.filter(Enum.map([1, 2, 3, 4, 5], fn x -> x * 2 end), fn x -> x > 4 end))

# Con pipe: pipeline leggibile dall'alto in basso
result =
  [1, 2, 3, 4, 5]
  |> Enum.map(fn x -> x * 2 end)   # [2, 4, 6, 8, 10]
  |> Enum.filter(fn x -> x > 4 end) # [6, 8, 10]
  |> Enum.sum()                      # 24

# Esempio reale: processamento di una lista di utenti
defmodule UserProcessor do
  def process_active_users(users) do
    users
    |> Enum.filter(&active?/1)           # Solo utenti attivi
    |> Enum.sort_by(& &1.name)            # Ordina per nome
    |> Enum.map(&enrich_with_metadata/1)  # Aggiungi metadata
    |> Enum.take(50)                      # Top 50
  end

  defp active?(%{status: :active}), do: true
  defp active?(_), do: false

  defp enrich_with_metadata(user) do
    Map.put(user, :display_name, format_display_name(user))
  end

  defp format_display_name(%{name: name, city: city}) do
    "#{name} (#{city})"
  end
  defp format_display_name(%{name: name}) do
    name
  end
end

users = [
  %{name: "Mario", status: :active, city: "Milano"},
  %{name: "Luigi", status: :inactive, city: "Roma"},
  %{name: "Peach", status: :active, city: "Torino"},
]

UserProcessor.process_active_users(users)
# [
#   %{name: "Mario", status: :active, city: "Milano", display_name: "Mario (Milano)"},
#   %{name: "Peach", status: :active, city: "Torino", display_name: "Peach (Torino)"},
# ]

Neměnnost: Data, která se nikdy nemění

V Elixir jsou datové struktury neměnné: nemůžete upravit seznam, existující mapu nebo n-tice. Funkce vždy vracejí nové struktury. To eliminuje celou třídu chyb (vedlejší účinky, sdílený proměnlivý stav) a to je to, co dělá konkurenci BEAM tak robustní.

# Immutabilita': le operazioni restituiscono nuovi dati

# Liste
original = [1, 2, 3]
new_list = [0 | original]   # Prepend: [0, 1, 2, 3]
original                     # Invariato: [1, 2, 3]

# Map: non puoi modificare, ottieni una nuova map
user = %{name: "Mario", age: 35}
updated_user = Map.put(user, :city, "Milano")
# updated_user = %{name: "Mario", age: 35, city: "Milano"}
user                # Ancora %{name: "Mario", age: 35}

# Syntactic sugar per update map
updated = %{user | age: 36}   # Aggiorna solo age
# %{name: "Mario", age: 36}
# NOTA: questa sintassi fallisce se la chiave non esiste

# Rebinding: le variabili possono essere riassegnate nello stesso scope
x = 1
x = x + 1   # x e' ora 2 (ma il valore 1 non e' cambiato)
# Questo NON e' mutazione: e' binding di x a un nuovo valore

# Con il pin operator, previeni il rebinding accidentale
y = 42
case some_value do
  ^y -> "Uguale a 42"  # Match solo se some_value == 42
  _ -> "Diverso"
end

Moduly a funkce

# Definizione moduli e funzioni
defmodule MyApp.Calculator do
  @moduledoc """
  Modulo di esempio per operazioni aritmetiche.
  """

  # Funzione pubblica: doc + type spec
  @doc "Somma due numeri interi."
  @spec add(integer(), integer()) :: integer()
  def add(a, b), do: a + b

  # Funzione con guards
  @spec divide(number(), number()) :: {:ok, float()} | {:error, String.t()}
  def divide(_, 0), do: {:error, "Division by zero"}
  def divide(a, b), do: {:ok, a / b}

  # Funzione privata (non accessibile fuori dal modulo)
  defp validate_positive(n) when n > 0, do: :ok
  defp validate_positive(_), do: :error

  # Funzioni anonime
  def run_examples do
    double = fn x -> x * 2 end
    triple = &(&1 * 3)   # Capture syntax: shorthand per fn

    IO.puts(double.(5))   # 10  (nota il . per chiamare fn anonima)
    IO.puts(triple.(5))   # 15

    # Higher-order functions
    [1, 2, 3, 4, 5]
    |> Enum.map(double)    # Passa funzione anonima come argomento
    |> IO.inspect()        # [2, 4, 6, 8, 10]
  end
end

# Uso
MyApp.Calculator.add(3, 4)      # 7
MyApp.Calculator.divide(10, 2)  # {:ok, 5.0}
MyApp.Calculator.divide(10, 0)  # {:error, "Division by zero"}

# Module attributes: costanti compile-time
defmodule Config do
  @max_retries 3
  @base_url "https://api.example.com"
  @supported_currencies [:EUR, :USD, :GBP]

  def max_retries, do: @max_retries
  def base_url, do: @base_url
end

Enum a Stream: Zpracování kolekce

# Enum: operazioni eager su liste (calcola subito)
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Enum.map(numbers, fn x -> x * x end)
# [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

Enum.filter(numbers, &(rem(&1, 2) == 0))
# [2, 4, 6, 8, 10]  -- solo pari

Enum.reduce(numbers, 0, fn x, acc -> acc + x end)
# 55  -- somma totale

Enum.group_by(numbers, &(rem(&1, 3)))
# %{0 => [3, 6, 9], 1 => [1, 4, 7, 10], 2 => [2, 5, 8]}

# Stream: operazioni lazy (calcola solo quando necessario)
# Utile per collection grandi o infinite
result =
  Stream.iterate(0, &(&1 + 1))    # Lista infinita: 0, 1, 2, 3, ...
  |> Stream.filter(&(rem(&1, 2) == 0))  # Solo pari (lazy)
  |> Stream.map(&(&1 * &1))             # Quadrati (lazy)
  |> Enum.take(5)                        # Prendi i primi 5 (trigger computation)
# [0, 4, 16, 36, 64]

# Stream da file: legge una riga alla volta (memory-efficient)
# File.stream!("large_file.csv")
# |> Stream.map(&String.trim/1)
# |> Stream.filter(&String.contains?(&1, "2026"))
# |> Enum.to_list()

Závěry

Funkční paradigma Elixíru není omezení: je základ, který umožňuje všechno ostatní. Odstranění shody vzoru vnořené podmíněné větve. Operátor potrubí vykreslí transformace dat čitelných jako próza. Neměnnost zajišťuje, že funkce nemají skryté vedlejší účinky. Tyto principy v kombinaci s BEAM VM, díky nim je Elixir tak vhodný pro distribuované systémy vysoká dostupnost.

Připravované články ze série Elixír

  • Článek 2: BEAM a procesy — masivní souběžnost bez sdílených vláken
  • Článek 3: OTP a GenServer — Distribuovaný stav s vestavěnou odolností proti chybám
  • Článek 4: Stromy dohledu — Navrhování systémů, které nikdy neumírají