Мультиме́тод () или мно́жественная диспетчериза́ция () — механизм в языках программирования, позволяющий выбрать одну из нескольких функций в зависимости от динамических типов или значений аргументов (например, перегрузка методов в некоторых языках программирования). Представляет собой расширение одиночной диспетчеризации (виртуальных функций), где выбор метода осуществляется динамически на основе фактического типа объекта, для которого этот метод был вызван. Множественная диспетчеризация обобщает динамическую диспетчеризацию для случаев с двумя или более объектами.

В явном виде мультиметоды поддерживаются «объектной системой Common Lisp’а» (CLOS).

Основы диспетчеризации Разработчики программ, как правило, группируют исходный код в именованные блоки, называемые вызовами, процедурами, подпрограммами, функциями или методами. Код функции выполняется путём её вызова, который заключается в выполнении фрагмента кода, обозначенного её именем. При этом управление временно передаётся вызываемой функции; когда выполнение этой функции завершается, управление обычно передаётся обратно команде, следующей после вызова функции.

Имена функций обычно выбираются так, чтобы описывать их назначение. Иногда нужно назвать несколько функций одним и тем же именем — как правило из-за того, что они выполняют концептуально схожие задачи, но работают с разными типами входных данных. В таких случаях одного имени функции в месте её вызова недостаточно для определения вызываемого блока кода. В дополнение к имени в этом случае для выбора конкретной реализации функции также используются количество и тип аргументов вызываемой функции.

В более традиционных объектно-ориентированных языках программирования с одиночной диспетчеризацией при вызове метода (отправке сообщения в Smalltalk, вызове функции-члена в C++), один из его аргументов рассматривается особым образом и используется для определения того, какой из (потенциально многих) методов с этим именем должен быть вызван. Во многих языках этот особый аргумент обозначается синтаксически, например, в ряде языков программирования специальный аргумент помещается перед точкой при вызове метода: special.method (other, arguments, here) так что lion.sound() будет давать рев, а sparrow.sound() будет давать чириканье.

В отличие от них, в языках с множественной диспетчеризацией выбираемый метод — это просто тот метод, аргументы которого совпадают с числом и типом аргументов в вызове функции. Здесь нет особого аргумента, который «владеет» функцией или методом, на которые ссылается конкретный вызов.

Common Lisp Object System (CLOS) является одной из первых и хорошо известных реализаций множественной диспетчеризации.

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

В языках программирования, которые определяют типы данных во время выполнения программы (позднее связывание), выбор среди вариантов функций должен происходить во время выполнения на основе динамически определяемых типов аргументов функций. Функции, альтернативные реализации которых выбираются таким образом, обычно называются мультиметодами.

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

Практическое использование Для того чтобы оценить, насколько часто множественная диспетчеризация используется на практике, Мушевичи (Muschevici) с соавторамиMuschevici, Radu; Potanin, Alex; Tempero, Ewan; Noble, James (2008). «Multiple dispatch in practice». Proceedings of the 23rd ACM SIGPLAN conference on Object-oriented programming systems languages and applications. OOPSLA '08 (Nashville, TN, USA: ACM): 563—582 исследовал приложения, использующие динамическую диспетчеризацию. Они проанализировали девять приложений, в основном компиляторов, написанных на шести различных языках программирования: Common Lisp Object System, Dylan, Cecil, MultiJava, Diesel и Nice. Результаты показывают, что от 13 % до 32 % обобщенных функций используют динамическую типизацию одного аргумента, в то время как от 2,7 % до 6,5 % функций используют динамическую типизацию нескольких аргументов. Остальные 65 %-93 % обобщенных функций имеют один конкретный метод (перегружены), и, таким образом, не рассматривались как использующие динамическую типизацию своих аргументов. Кроме того, в исследовании сообщается, что от 2 % до 20 % обобщенных функций имели две, а 3 %-6 % имели три свои конкретные реализации. Доля функций с большим числом конкретных реализаций быстро уменьшалась.

Теория Теория языков с мультивызовами впервые была разработана Кастаньей (Castagna) с соавторами, путём определения модели для перегруженных функций с поздним связываниемGiuseppe Castagna; Giorgio Ghelli & Giuseppe Longo (1995). «A calculus for overloaded functions with subtyping.» . Information and Computation (Academic press) 117 (1): 115—135Castagna, Giuseppe (1996). Object-Oriented Programming: A Unified Foundation. Birkhäuser. p. 384.. Это дало первую формализацию проблемы ковариантности и контравариантности объектно ориентированных языков программированияGiuseppe Castagna (1995). «Covariance and contravariance: conflict without a cause» . Transactions on Programming Languages and Systems (TOPLAS) (ACM) 17 (3). doi:10.1145/203095.203096 и решение проблемы бинарных методовKim Bruce; Luca Cardelli; Giuseppe Castagna; Gary T. Leavens; Benjamin Pierce (1995). «On binary methods» . Theory and Practice of Object Systems 1 (3).

Пример Для лучшего понимания разницы между мультиметодами и одиночной диспетчеризацией можно продемонстрировать следующий пример. Представим себе игру, в которой, наряду с разнообразными другими объектами, присутствуют астероиды и космические корабли. Когда два каких-либо объекта сталкиваются, программа должна выбрать какой-либо определенный алгоритм действий, в зависимости от того, что столкнулось с чем.

Common Lisp В языке с поддержкой мультиметодов, таком, как Common Lisp, код выглядел бы вот так:

(defgeneric collide (x y))

(defmethod collide ((x asteroid) (y asteroid)) ;;астероид сталкивается с астероидом )

(defmethod collide ((x asteroid) (y spaceship)) ;;астероид сталкивается с космическим кораблем )

(defmethod collide ((x spaceship) (y asteroid)) ;;космический корабль сталкивается с астероидом )

(defmethod collide ((x spaceship) (y spaceship)) ;;космический корабль сталкивается с космическим кораблем )

и аналогично для других методов. Явная проверка и «динамическое приведение типов» здесь не используются. 

При наличии множественной диспетчеризации традиционный подход к определению методов в классах и хранению их в объектах становится менее привлекательным, поскольку каждый метод collide-with относится к двум различным классам, а не к одному. Таким образом, специальный синтаксис для вызова метода, в общем случае, исчезает, так что вызов метода выглядит точно также, как вызов обычной функции, и методы группируются не по классам, а в обобщенных функциях.

Raku В Raku, как и в предыдущих его версиях, используются проверенные идеи из других языков и системы типов, предлагающие убедительные преимущества в анализе кода со стороны компилятора и мощной семантике путём множественной диспетчеризации.

В нём есть как мультиметоды, так и мультиподпрограммы. Так как большинство операторов являются подпрограммами, есть также операторы с множественной диспетчеризацией.

Наряду с обычными ограничениями типов в нём есть также ограничения типа «где», что позволяет создавать очень специализированные подпрограммы.

subset Mass of Real where 0 ^..^ Inf; role Stellar-Object { has Mass $.mass is required; method name () returns Str {...}; } class Asteroid does Stellar-Object { method name () { 'an asteroid' } } class Spaceship does Stellar-Object { has Str $.name = 'some unnamed spaceship'; } my Str @destroyed = < obliterated destroyed mangled >; my Str @damaged = « damaged 'collided with' 'was damaged by' »;

# We add multi candidates to the numeric comparison operators because we are comparing them numerically, # but doesn't make sense to have the objects coerce to a Numeric type. # ( If they did coerce we wouldn't necessarily need to add these operators. ) # We could have also defined entirely new operators this same way. multi sub infix:« <=> » ( Stellar-Object:D $a, Stellar-Object:D $b ) { $a.mass <=> $b.mass } multi sub infix:« < » ( Stellar-Object:D $a, Stellar-Object:D $b ) { $a.mass < $b.mass } multi sub infix:« > » ( Stellar-Object:D $a, Stellar-Object:D $b ) { $a.mass > $b.mass } multi sub infix:« == » ( Stellar-Object:D $a, Stellar-Object:D $b ) { $a.mass == $b.mass }

# Define a new multi dispatcher, and add some type constraints to the parameters. # If we didn't define it we would have gotten a generic one that didn't have constraints. proto sub collide ( Stellar-Object:D $, Stellar-Object:D $ ) {*}

# No need to repeat the types here since they are the same as the prototype. # The 'where' constraint technically only applies to $b not the whole signature. # Note that the 'where' constraint uses the `<` operator candidate we added earlier. multi sub collide ( $a, $b where $a < $b ) { say "$a.name() was @destroyed.pick() by $b.name()"; } multi sub collide ( $a, $b where $a > $b ) { # redispatch to the previous candidate with the arguments swapped samewith $b, $a; }

# This has to be after the first two because the other ones # have 'where' constraints, which get checked in the # order the subs were written. ( This one would always match. ) multi sub collide ( $a, $b ){ # randomize the order my ($n1,$n2) = ( $a.name, $b.name ).pick(*); say "$n1 @damaged.pick() $n2"; }

# The following two candidates can be anywhere after the proto, # because they have more specialized types than the preceding three.

# If the ships have unequal mass one of the first two candidates gets called instead. multi sub collide ( Spaceship $a, Spaceship $b where $a == $b ){ my ($n1,$n2) = ( $a.name, $b.name ).pick(*); say "$n1 collided with $n2, and both ships were ", ( @destroyed.pick, 'left damaged' ).pick; }

# You can unpack the attributes into variables within the signature. # You could even have a constraint on them `(:mass($a) where 10)`. multi sub collide ( Asteroid $ (:mass($a)), Asteroid $ (:mass($b)) ){ say "two asteroids collided and combined into one larger asteroid of mass { $a + $b }"; }

my Spaceship $Enterprise .= new(:mass(1),:name('The Enterprise')); collide Asteroid.new(:mass(.1)), $Enterprise; collide $Enterprise, Spaceship.new(:mass(.1)); collide $Enterprise, Asteroid.new(:mass(1)); collide $Enterprise, Spaceship.new(:mass(1)); collide Asteroid.new(:mass(10)), Asteroid.new(:mass(5));

Python В языках, которые не поддерживают множественную диспетчеризацию на уровне синтаксиса, таких как Python, как правило, возможно использовать множественную диспетчеризацию с помощью библиотек расширений. Например, модуль multimethods.pymultimethods.py , Multiple dispatch in Python with configurable dispatch resolution by David Mertz, et al. реализует мультиметоды в духе CLOS в Python без изменения синтаксиса или ключевых слов языка.

from multimethods import Dispatch from game_objects import Asteroid, Spaceship from game_behaviors import ASFunc, SSFunc, SAFunc collide = Dispatch() collide.add_rule((Asteroid, Spaceship), ASFunc) collide.add_rule((Spaceship, Spaceship), SSFunc) collide.add_rule((Spaceship, Asteroid), SAFunc) def AAFunc(a, b): """Behavior when asteroid hits asteroid""" # ...define new behavior...