스크래치의 비약: 기능적 패러다임, 패턴 일치 및 파이프 연산자
OOP 배경을 가진 개발자를 위한 Elixir 소개: 패턴 매칭과 같은 우아한 구성을 위해 고전적인 if/switch인 파이프 연산자 |>를 대체합니다. 데이터 불변성과 이것이 더 예측 가능한 코드로 이어지는 이유.
2026년에 Elixir를 선택해야 하는 이유
Elixir는 "단지 또 다른 기능적 언어"가 아닙니다. 그것은 위에 세워졌습니다 Ericsson이 1980년대에 설계한 시스템인 Erlang의 BEAM 가상 머신 99.9999999% 가동 시간으로 수백만 개의 동시 전화 연결을 관리합니다. Elixir는 이를 기반으로 현대적인 구문인 Mix/Hex 툴체인을 제공합니다. Phoenix Framework — 최고의 성능을 자랑하는 웹 프레임워크 중 하나 부문에 문서화되어 있습니다.
Elixir의 기능적 패러다임은 미적인 선택이 아닙니다. 내결함성과 대규모 동시성을 가능하게 하는 요소 빔의. OTP 및 GenServer(이후 기사)를 이해하려면 먼저 다음을 수행해야 합니다. 이러한 기본 사항을 숙지하세요.
무엇을 배울 것인가
- Mix, IEx(대화형 REPL)를 사용한 설치 및 설정
- 기본 유형: 원자, 튜플, 목록, 맵, 키워드 목록
- 패턴 매칭: Elixir의 가장 강력한 기능
- 파이프 연산자 |>: 읽을 수 있는 함수 구성
- 불변성: 변수를 변경할 수 없기 때문입니다.
- 함수: 명명된 함수, 익명 함수, 고차 함수
- 모듈: Elixir에서 코드를 구성하는 방법
설치 및 설정
# 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
기본 유형 및 데이터 구조
# 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]
패턴 매칭: 엘릭서 코어
엘릭서에서는 = 그것은 할당이 아니라 작업이다
의 성냥. 왼쪽은 오른쪽과 "일치"됩니다.
일치에 성공하면 패턴의 변수가 해당 값에 바인딩됩니다.
실패하면 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
파이프 연산자: 변환 구성
파이프 연산자 |> 이전 표현식의 결과를 전달합니다.
다음 함수의 첫 번째 인수로 사용됩니다. 중첩 호출 변환
읽을 수 있는 선형 시퀀스로 — 코드는 파이프라인을 표현합니다.
발생하는 순서대로 변환됩니다.
# 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)"},
# ]
불변성: 절대 변하지 않는 데이터
Elixir에서는 데이터 구조가 불변입니다. 목록을 수정할 수 없습니다. 기존 맵 또는 튜플. 함수는 항상 새로운 구조를 반환합니다. 이는 전체 종류의 버그(부작용, 공유 변경 가능 상태)를 제거합니다. 이것이 바로 BEAM의 경쟁이 그토록 강력한 이유입니다.
# 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
모듈 및 기능
# 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: 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()
결론
Elixir의 기능적 패러다임은 제한이 아닙니다. 다른 모든 것을 가능하게 하는 기초. 패턴 매칭 제거 중첩된 조건부 분기. 파이프 연산자는 변환을 렌더링합니다. 산문으로 읽을 수 있는 데이터. 불변성은 다음 기능을 보장합니다. 숨겨진 부작용이 없습니다. 이러한 원칙은 BEAM VM과 결합되어 이것이 바로 Elixir를 분산 시스템에 적합하게 만드는 이유입니다. 고가용성.
Elixir 시리즈의 향후 기사
- 제2조: BEAM 및 프로세스 — 공유 스레드가 없는 대규모 동시성
- 제3조: OTP 및 GenServer - 내결함성이 내장된 분산 상태
- 제4조: 감독자 트리 — 절대 죽지 않는 시스템 설계







