Elixir de la zero: paradigmă funcțională, potrivire a modelelor și operator de conducte
Introducere în Elixir pentru dezvoltatorii cu fundal OOP: cum ar fi potrivirea modelelor înlocuiește clasicul if/switch, operatorul conductei |> pentru compoziție elegantă, imuabilitatea datelor și de ce acest lucru duce la un cod mai previzibil.
De ce Elixir în 2026
Elixir nu este „doar un alt limbaj funcțional”. Este construit pe Mașina virtuală BEAM a lui Erlang, un sistem conceput în anii 1980 de Ericsson pentru a gestiona milioane de conexiuni telefonice simultane cu un timp de funcționare de 99,9999999%. Elixir aduce pe această bază o sintaxă modernă, lanțul de instrumente Mix/Hex și Phoenix Framework — unul dintre cele mai performante cadre web documentate în sector.
Paradigma funcțională a Elixirului nu este o alegere estetică: este ceea ce face posibilă toleranța la erori și concurența masivă de GRANDĂ. Pentru a înțelege OTP și GenServer (articolele ulterioare), mai întâi trebuie stăpânesc aceste elemente fundamentale.
Ce vei învăța
- Instalare și configurare cu Mix, IEx (REPL interactiv)
- Tipuri de bază: atom, tuplu, listă, hartă, listă de cuvinte cheie
- Potrivirea modelelor: cea mai puternică caracteristică a Elixirului
- Operator pipe |>: Compoziție lizibilă a funcției
- Imuabilitate: Pentru că nu poți schimba o variabilă
- Funcții: funcții cu nume, anonime și de ordin superior
- Module: Cum se organizează codul în Elixir
Instalare și configurare
# 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
Tipuri de bază și structuri de date
# 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]
Potrivirea modelelor: Miezul Elixirului
În Elixir, = nu este o misiune: este o operațiune
de meci. Partea stângă este „potrivită” cu partea dreaptă.
Dacă potrivirea reușește, variabilele din model sunt legate la valorile corespunzătoare.
Dacă eșuează, a este ridicat 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
Operator de conducte: Compoziția transformărilor
Operatorul de conducte |> trece rezultatul expresiei anterioare
ca prim argument al funcției următoare. Transformați apelurile imbricate
într-o secvență liniară care poate fi citită — codul exprimă o conductă de
transformări în ordinea în care apar.
# 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)"},
# ]
Imuabilitate: date care nu se modifică niciodată
În Elixir, structurile de date sunt imuabile: nu puteți modifica o listă, o hartă sau un tuplu existent. Funcțiile returnează întotdeauna structuri noi. Acest lucru elimină o întreagă clasă de erori (efecte secundare, stare mutabilă partajată) și asta face concurența BEAM atât de robustă.
# 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
Module și funcții
# 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 și Stream: Procesarea colecției
# 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()
Concluzii
Paradigma funcțională a Elixirului nu este o restricție: este fundația care face totul posibil. Se elimină potrivirea modelelor ramuri condiționale imbricate. Operatorul conductei redă transformările de date care pot fi citite ca proză. Imuabilitatea asigură că funcțiile nu au efecte secundare ascunse. Aceste principii, combinate cu BEAM VM, acestea sunt ceea ce face ca Elixir să fie atât de potrivit pentru sistemele distribuite disponibilitate ridicată.
Articole viitoare din seria Elixir
- Articolul 2: BEAM și procese — Concurență masivă fără fire partajate
- Articolul 3: OTP și GenServer — Stare distribuită cu toleranță la erori încorporată
- Articolul 4: Arbori de supraveghere — Proiectarea sistemelor care nu mor niciodată







