Как работает система типов в Elixir?

Система типов в Elixir является динамической и слаботипизированной, что означает, что типы данных определяются во время выполнения, а не во время компиляции. Это делает язык гибким и удобным для быстрой разработки, особенно в функциональном стиле. В то же время Elixir предоставляет средства аннотирования типов через @spec и проверки типов во время тестирования и анализа, что позволяет контролировать поведение программы.

Основные характеристики системы типов в Elixir

1. Динамическая типизация

Тип переменной определяется в момент присваивания значения и может изменяться в зависимости от контекста. Нет жёсткой привязки переменной к какому-либо конкретному типу:

x = 42 # x — integer
x = "Hello" # x теперь string

2. Неявная типизация

Разработчику не нужно объявлять типы переменных. Интерпретатор (BEAM VM) сам определяет тип значения в рантайме.

Примитивные типы данных в Elixir

  • Целые числа (integer) — например, 42, -7

  • Числа с плавающей точкой (float) — 3.14, -0.5

  • Булевы значения (boolean) — true, false

  • Атомы (atom) — символы, представляющие идентификаторы: :ok, :error

  • Строки (binary/utf-8) — строки в двойных кавычках: "hello"

  • Списки (list) — упорядоченные коллекции: [1, 2, 3]

  • Кортежи (tuple) — фиксированные по размеру наборы значений: {:ok, 123}

  • Map (карты) — структуры вида %{ключ => значение}

  • Struct (структуры) — надстройки над картами с определённым набором ключей

  • Bitstring / Binary — используются для работы с побитовыми значениями

  • Function — first-class значения, которые можно передавать, хранить, вызывать

Спецификации типов: @spec

Для документирования и анализа типов Elixir использует аннотации @spec, в которых указывается, какие типы принимает и возвращает функция. Они проверяются утилитой Dialyzer — статическим анализатором, работающим поверх BEAM.

@spec add(integer(), integer()) :: integer()
def add(a, b), do: a + b

Эта спецификация указывает, что add/2 принимает два целых числа и возвращает целое число.

Статическая проверка типов через Dialyzer

Elixir не применяет строгую проверку типов на этапе компиляции, но вы можете использовать Dialyzer для анализа кода:

  • Он выявляет ошибки совместимости типов

  • Проверяет спецификации @spec

  • Выявляет "dead code", невозможные ветки сопоставления

  • Помогает при рефакторинге, особенно в больших проектах

Dialyzer не обязывает к аннотациям, но работает лучше при их наличии.

Сопоставление с образцом (Pattern Matching) и типы

Pattern matching в Elixir тесно связан с системой типов. Он позволяет точно проверять, к какому типу относится значение, и разветвлять выполнение:

def handle_result({:ok, value}), do: IO.puts("Result: #{value}")
def handle_result(:error), do: IO.puts("Something went wrong")

Здесь тип значения задаётся через форму — кортеж {:ok, value} или атом :error.

Типы в структурах (Structs)

Struct — это карта (map) с фиксированным набором ключей и встроенной защитой от использования "неправильных" полей.

defmodule User do
defstruct \[:name, :age\]
end
%User{name: "Alex", age: 30}

Можно описывать @type для структуры:

@type t :: %User{name: String.t(), age: non_neg_integer()}

@type и @typedoc

Elixir позволяет создавать псевдонимы типов:

@type id :: integer()
@type name :: String.t()
@type user :: %{id: id(), name: name()}

И использовать их в @spec, улучшая читаемость кода:

@spec greet_user(user()) :: String.t()
def greet_user(%{name: name}), do: "Hello, #{name}"

Обработка разных типов (guard'ы)

Elixir предлагает набор guard-функций — предикатов для фильтрации данных по типам:

  • is_integer/1

  • is_float/1

  • is_binary/1

  • is_atom/1

  • is_list/1

  • is_map/1

  • is_tuple/1

  • и др.

def double(x) when is_integer(x), do: x \* 2
def double(x) when is_float(x), do: Float.round(x \* 2, 2)

Инструменты типизации и проверки:

  • Dialyzer — основной статический анализатор типов

  • TypeCheck — библиотека для контрактов и проверки типов во время исполнения

  • Norm — декларативное описание схем данных и их проверка

Таким образом, система типов в Elixir — гибкая, динамическая, но при этом предоставляющая разработчику инструменты для документирования, анализа и валидации типов. Она не мешает быстрому прототипированию, но при необходимости позволяет усилить надёжность через аннотации и проверку.