Sıfırdan İksir: İşlevsel Paradigma, Desen Eşleştirme ve Boru Operatörü
OOP geçmişi olan geliştiriciler için Elixir'e giriş: model eşleştirme gibi zarif kompozisyon için klasik if/switch'in, boru operatörünün |> yerini alır, veri değişmezliği ve bunun neden daha öngörülebilir kodlara yol açtığı.
Neden 2026'da İksir
İksir "sadece başka bir işlevsel dil" değildir. Bunun üzerine inşa edilmiştir Erlang'ın BEAM Sanal Makinesi, 1980'lerde Ericsson tarafından tasarlanan bir sistem Milyonlarca eşzamanlı telefon bağlantısını %99,9999999 kesintisiz çalışma süresiyle yönetmek. Elixir bu temelde modern bir sözdizimi olan Mix/Hex araç zincirini ve Phoenix Framework — en iyi performans gösteren web çerçevelerinden biri sektörde belgelenmiştir.
Elixir'in işlevsel paradigması estetik bir seçim değildir: Hata toleransını ve büyük eşzamanlılığı mümkün kılan şey nedir? BEAM'den. OTP ve GenServer'ı (sonraki makaleler) anlamak için öncelikle şunları yapmanız gerekir: Bu temel bilgilere hakim olun.
Ne Öğreneceksiniz
- Mix, IEx (etkileşimli REPL) ile kurulum ve kurulum
- Temel türler: atom, tuple, liste, harita, anahtar kelime listesi
- Desen eşleştirme: Elixir'in en güçlü özelliği
- Boru operatörü |>: Okunabilir fonksiyon bileşimi
- Değişmezlik: Çünkü bir değişkeni değiştiremezsiniz
- İşlevler: adlandırılmış, anonim ve üst düzey işlevler
- Modüller: Elixir'de kod nasıl organize edilir
Kurulum ve Kurulum
# 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
Temel Türler ve Veri Yapıları
# 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]
Desen Eşleştirme: İksir Çekirdeği
İksir'de, = bu bir görev değil: bu bir operasyon
arasında kibrit. Sol taraf sağ tarafla "eşleşir".
Eşleşme başarılı olursa modeldeki değişkenler karşılık gelen değerlere bağlanır.
Başarısız olursa, a yükseltilir 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
Boru Operatörü: Dönüşümlerin Bileşimi
Boru operatörü |> önceki ifadenin sonucunu iletir
sonraki fonksiyonun ilk argümanı olarak. İç içe çağrıları dönüştürün
okunabilir bir doğrusal sırayla — kod, bir boru hattını ifade eder
dönüşümler meydana gelme sırasına göre yapılır.
# 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)"},
# ]
Değişmezlik: Asla Değişmeyen Veriler
Elixir'de veri yapıları değişmez: bir listeyi değiştiremezsiniz, mevcut bir harita veya tuple. Fonksiyonlar her zaman yeni yapılar döndürür. Bu, bir dizi hatayı ortadan kaldırır (yan etkiler, paylaşılan değiştirilebilir durum) BEAM'in rekabetini bu kadar güçlü kılan da bu.
# 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
Modüller ve Fonksiyonlar
# 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 ve Akış: Koleksiyon İşleme
# 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()
Sonuçlar
Elixir'in işlevsel paradigması bir kısıtlama değildir: diğer her şeyi mümkün kılan temel. Desen eşleştirme ortadan kaldırılıyor iç içe koşullu dallar. Boru operatörü dönüşümleri gerçekleştirir düzyazı olarak okunabilen veriler. Değişmezlik, işlevlerin gizli yan etkileri yoktur. Bu ilkeler BEAM VM ile birleştirildiğinde, Elixir'i dağıtılmış sistemler için bu kadar uygun kılan şey bunlardır yüksek kullanılabilirlik.
İksir Serisinde Gelecek Makaleler
- Madde 2: BEAM ve Süreçler — Paylaşılan Konular Olmadan Büyük Eşzamanlılık
- Madde 3: OTP ve GenServer — Yerleşik Hata Toleransı ile Dağıtılmış Durum
- Madde 4: Denetleyici Ağaçlar — Asla Ölmeyen Sistemler Tasarlamak







