Параметрические типы

Параметрические типы — шаблоны вроде Vector{Int}, которые дают и обобщённость, и скорость.

Параметрический тип — это тип-шаблон с параметром в фигурных скобках, например Vector{T}, где T — тип элементов.

Что значат фигурные скобки

Вы уже встречали записи вроде Vector{Int64} или Complex{Float64}. Часть в фигурных скобках — это параметр типа. Vector{Int64} и Vector{Float64} — два разных конкретных типа, порождённых одним шаблоном Vector{T}.

a = [1, 2, 3]
b = [1.0, 2.0, 3.0]
println(typeof(a))
println(typeof(b))
println(eltype(a))     # тип элементов

Вывод:

Vector{Int64}
Vector{Float64}
Int64

Зачем это нужно

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

Свои параметрические типы

Структуру тоже можно сделать обобщённой по типу полей:

struct Pair2{T}
    first::T
    second::T
end

p_int = Pair2(1, 2)
p_str = Pair2("a", "b")
println(typeof(p_int))
println(typeof(p_str))

Вывод:

Pair2{Int64}
Pair2{String}

Здесь один шаблон Pair2{T} породил Pair2{Int64} и Pair2{String}. Поля имеют конкретный тип T, поэтому каждая версия компактна и быстра.

Ограничения параметров

Параметр можно ограничить подтипом: Vector{<:Number} — «вектор, элементы которого являются числами». В сигнатуре функции это пишут так:

total(v::Vector{T}) where {T <: Number} = sum(v)
println(total([1, 2, 3]))
println(total([1.5, 2.5]))

Вывод:

6
4.0

Как работает под капотом

Это и есть технический ответ на вопрос «почему Julia быстрая». Когда параметр T известен (например, Vector{Int64}), компилятор знает, что каждый элемент занимает ровно 8 байт и лежит в памяти подряд. Он генерирует машинный код, работающий с этим типом без «упаковки» (boxing) и проверок типа в рантайме — как компилятор C для массива int. Сравните с Python-списком, где каждый элемент — отдельный объект с тегом типа, и интерпретатор проверяет тип на каждой операции.

Частые ошибки

Тонкость: Vector{Number} и Vector{<:Number} — разные вещи. Vector{Number} хранит элементы абстрактного типа (медленно, как «список чего угодно числового»), а Vector{<:Number} — это семейство конкретных типов вроде Vector{Int64}. Для скорости вам почти всегда нужен второй вариант. Не пишите контейнеры с абстрактным параметром в производительном коде.

Итоги

  • Параметрический тип — шаблон с параметром: Vector{T}, Complex{T}.
  • Vector{Int64} и Vector{Float64} — разные конкретные типы из одного шаблона.
  • Свои структуры тоже делают обобщёнными: struct Pair2{T}.
  • Параметр можно ограничить: where {T <: Number}.
  • Знание конкретного T позволяет компилятору сгенерировать быстрый код без проверок типа в рантайме.
Проверьте себя
1. Что обозначает T в записи Vector{T}?
AДлину вектора
BПараметр типа — тип элементов вектора
CИмя переменной
DПризнак транспонирования
2. Почему Vector{Int64} обрабатывается быстрее Python-списка целых?
AJulia запускает его в облаке
BКомпилятор знает точный размер элемента и генерирует код без проверок типа в рантайме
CСписок Python всегда пустой
DVector{Int64} хранит меньше элементов