Множественная диспетчеризация — сердце Julia
Главная парадигма Julia — выбор реализации функции по типам сразу всех её аргументов.
Множественная диспетчеризация (multiple dispatch) — механизм, при котором конкретная реализация функции выбирается на основе типов всех переданных аргументов, а не только одного объекта.
В чём идея
В объектно-ориентированных языках вроде Python метод привязан к одному объекту: cat.speak() — реализация выбирается по типу cat. Это «одиночная диспетчеризация» — решение по одному (первому) аргументу. В Julia функция — это набор методов, и нужный метод выбирается по комбинации типов всех аргументов.
Несколько методов одной функции
Определим функцию interact для разных пар животных:
interact(a, b) = "$a и $b просто рядом"
interact(a::String, b::Int) = "строка $a повторяется $b раз"
println(interact("кот", "пёс"))
println(interact("ха", 3))Вывод:
кот и пёс просто рядом строка ха повторяется 3 раз
Запись a::String означает «этот метод подходит, только если a — строка». Julia сама выбирает самый специфичный подходящий метод по типам аргументов.
Классический пример: операторы
Само сложение + в Julia — функция с множеством методов: один для двух Int, другой для двух Float64, третий для Int и Float64, для комплексных, для матриц и т. д. Когда вы пишете 2 + 3.0, диспетчеризация выбирает метод для пары (Int, Float64). Вы можете добавить свой метод + для собственных типов — и встроенный синтаксис заработает с ними.
Почему это мощно
Множественная диспетчеризация решает «проблему выражения»: легко добавлять и новые типы, и новые операции над существующими типами, не меняя чужой код. Поэтому пакеты Julia так хорошо сочетаются: автор библиотеки чисел и автор библиотеки графиков могут не знать друг о друге, но их типы будут работать вместе, если для нужных комбинаций определены методы.
Как работает под капотом
При вызове функции Julia смотрит на типы аргументов и в так называемой таблице методов находит самый специфичный из подходящих. В большинстве случаев типы известны на этапе компиляции, поэтому выбор метода происходит не во время выполнения, а заранее — это и быстро, и порождает специализированный машинный код. Посмотреть все методы функции можно командой methods(+) — для сложения их будут сотни.
Частые ошибки
Главное заблуждение пришедших из ООП — пытаться воссоздать классы и наследование «методов внутри объекта». В Julia нет методов, принадлежащих типу; есть свободные функции с методами под разные типы. Не пишите obj.method() — пишите method(obj). Вторая ошибка — определить слишком общий метод f(a, b), который перехватывает вызовы, предназначенные более специфичным методам из других пакетов; будьте конкретны в аннотациях типов там, где это важно.
Итоги
- Множественная диспетчеризация выбирает метод по типам всех аргументов.
- Это отличается от ООП-диспетчеризации по одному объекту (
obj.method()). - Функция — набор методов;
+сам реализован множеством методов. - Подход позволяет легко расширять и типы, и операции — пакеты Julia хорошо сочетаются.
- Выбор метода обычно происходит на этапе компиляции, что даёт скорость.