2026 年に Elixir を選択する理由

Elixir は「単なる関数型言語」ではありません。それは上に構築されています Erlang の BEAM 仮想マシン、1980 年代に Ericsson によって設計されたシステム 99.9999999% の稼働率で何百万もの同時電話接続を管理します。 Elixir は、これに基づいて最新の構文である Mix/Hex ツールチェーンをもたらします。 Phoenix Framework — 最もパフォーマンスの高い Web フレームワークの 1 つ セクター内で文書化されています。

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]

パターン マッチング: Elixir コア

エリクサーでは、 = それは割り当てではなく、操作です の マッチ。左側は右側と「一致」します。 一致が成功した場合、パターン内の変数は対応する値にバインドされます。 失敗すると 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 と Stream: コレクションの処理

# 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条: スーパーバイザー ツリー — 決して死なないシステムの設計