Множественная отправка или мультиметоды - это особенность некоторых языков программирования, в которых функция или метод могут быть отправлены динамически на основе времени выполнения (динамического) типа или, в более общем случае, какого-либо другого атрибута более чем одного из ее аргументов . [1] Это обобщение полиморфизма однократной отправки. где вызов функции или метода динамически отправляется на основе производного типа объекта, для которого был вызван метод. Множественная отправка направляет динамическую отправку к реализующей функции или методу, используя комбинированные характеристики одного или нескольких аргументов.
Понимание отправки
Разработчики компьютерного программного обеспечения обычно организуют исходный код в именованные блоки, которые по-разному называются подпрограммами , процедурами, подпрограммами, функциями или методами. Код в функции выполняется путем ее вызова - выполнения фрагмента кода, который ссылается на ее имя . Это временно передает управление вызываемой функции; когда выполнение функции завершено, управление обычно передается обратно инструкции в вызывающей программе, которая следует за ссылкой.
Имена функций обычно выбираются так, чтобы описывать назначение функции. Иногда желательно дать нескольким функциям одно и то же имя, часто потому, что они выполняют концептуально похожие задачи, но работают с разными типами входных данных. В таких случаях ссылки на имя на сайте вызова функции недостаточно для идентификации блока кода, который должен быть выполнен. Вместо этого количество и тип аргументов вызова функции также используются для выбора из нескольких реализаций функции.
В более традиционных, то есть объектно-ориентированных языках программирования с однократной отправкой , при вызове метода ( отправка сообщения на Smalltalk , вызов функции-члена на C ++ ) один из его аргументов обрабатывается специально и используется для определения того, какой из (потенциально многие) классы методов с таким именем должны применяться. Во многих языках специальный аргумент указывается синтаксически; например, некоторые языки программирования помещают специальный аргумент перед точкой при вызове метода:, special.method(other, arguments, here)
так что это lion.sound()
вызовет рев, тогда как sparrow.sound()
вызовет чириканье.
Напротив, в языках с множественной отправкой выбранный метод - это просто метод, аргументы которого соответствуют номеру и типу вызова функции. Нет специального аргумента, который владеет функцией / методом, выполняемым в конкретном вызове.
Объект Common Lisp System (CLOS) является ранним и хорошо известным примером множественной диспетчеризации.
Типы данных
При работе с языками, которые могут различать типы данных во время компиляции , тогда может происходить выбор среди альтернатив. Акт создания таких альтернативных функций для выбора во время компиляции обычно называется перегрузкой функции.
В языках программирования, которые откладывают идентификацию типа данных до времени выполнения (т. Е. Позднего связывания ), тогда должен происходить выбор среди альтернативных функций на основе динамически определяемых типов аргументов функции. Функции, альтернативные реализации которых выбираются таким образом, обычно называются мультиметодами .
Существует некоторая стоимость времени выполнения, связанная с динамической диспетчеризацией вызовов функций. В некоторых языках [ необходима цитата ] различие между перегрузкой и мультиметодами может быть размыто, поскольку компилятор определяет, можно ли применить выбор времени компиляции к данному вызову функции или требуется более медленная диспетчеризация во время выполнения.
Использование на практике
Чтобы оценить, насколько часто на практике используется множественная отправка, Muschevici et al. [2] изучали программы, использующие динамическую диспетчеризацию. Они проанализировали девять приложений, в основном компиляторы, написанные на шести разных языках: Common Lisp Object System , Dylan , Cecil , MultiJava, Diesel и Nice. Их результаты показывают, что 13–32% универсальных функций используют динамический тип одного аргумента, а 2,7–6,5% из них используют динамический тип нескольких аргументов. Остальные 65–93% универсальных функций имеют один конкретный метод (переопределитель) и, следовательно, не считаются использующими динамические типы своих аргументов. Кроме того, исследование сообщает, что 2–20% общих функций имели две и 3–6% имели три реализации конкретных функций. Числа быстро уменьшаются для функций с более конкретными переопределителями.
Множественная отправка используется гораздо более активно в Julia , где множественная отправка была центральной концепцией дизайна с момента зарождения языка: собирая ту же статистику, что и Muschevici по среднему количеству методов на общую функцию, было обнаружено, что стандартная библиотека Julia использует более чем в два раза превышает объем перегрузки, чем на других языках, проанализированных Muschevici, и более чем в 10 раз в случае бинарных операторов . [3]
Данные из этих документов сведены в следующую таблицу, где коэффициент диспетчеризации DR
- это среднее количество методов на общую функцию; коэффициент выбора CR
- это среднее значение квадрата количества методов (чтобы лучше измерить частоту функций с большим количеством методов); [2] [3], а степень специализации DoS
- это среднее количество аргументов, зависящих от типа, на метод (т. Е. Количество аргументов, которые отправляются):
Язык | Среднее количество методов (DR) | Коэффициент выбора (CR) | Степень специализации (DoS) |
---|---|---|---|
Сесил [2] | 2.33 | 63,30 | 1.06 |
Общий Лисп ( CMU ) [2] | 2,03 | 6,34 | 1.17 |
Общий Лисп ( McCLIM ) [2] | 2.32 | 15,43 | 1.17 |
Common Lisp ( Steel Bank ) [2] | 2.37 | 26,57 | 1.11 |
Дизель [2] | 2,07 | 31,65 | 0,71 |
Дилан (Гвидион) [2] | 1,74 | 18,27 | 2,14 |
Дилан (OpenDylan) [2] | 2,51 | 43,84 | 1,23 |
Юлия [3] | 5,86 | 51,44 | 1,54 |
Юлия (только операторы) [3] | 28,13 | 78,06 | 2,01 |
MultiJava [2] | 1,50 | 8,92 | 1.02 |
Хорошо [2] | 1,36 | 3,46 | 0,33 |
Теория
Теория нескольких языков диспетчеризации была впервые разработана Кастагной и др., Определив модель для перегруженных функций с поздним связыванием . [4] [5] Это дало первую формализацию проблемы ковариантности и контравариантности объектно-ориентированных языков [6] и решение проблемы бинарных методов. [7]
Примеры
Различение множественной и одиночной отправки может быть более ясным на примере. Представьте себе игру, в которой среди (видимых пользователем) объектов есть космические корабли и астероиды. Когда два объекта сталкиваются, программе может потребоваться выполнять разные действия в зависимости от того, что только что произошло.
Языки со встроенной множественной рассылкой
C #
В C # появилась поддержка динамических мультиметодов в версии 4 [8] (апрель 2010 г.) с использованием ключевого слова dynamic. В следующем примере демонстрируются мультиметоды в сочетании с выражениями переключения, представленными в версии 8 [9] (сентябрь 2019 г.). Как и многие другие языки со статической типизацией, C # также поддерживает перегрузку статических методов. [10] Microsoft ожидает, что разработчики предпочтут статическую типизацию вместо динамической в большинстве сценариев. [11] Ключевое слово dynamic поддерживает взаимодействие с COM-объектами и языками .NET с динамической типизацией.
class Program { static void Main () { Консоль . WriteLine ( Коллайдер . Столкновение ( новый астероид ( 101 ), новый космический корабль ( 300 ))); Консоль . WriteLine ( Коллайдер . Столкновение ( новый астероид ( 10 ), новый космический корабль ( 10 ))); Консоль . WriteLine ( Collider . Collide ( новый космический корабль ( 101 ), новый космический корабль ( 10 ))); } }статический класс Collider { общедоступная статическая строка Collide ( SpaceObject x , SpaceObject y ) => (( x . Size > 100 ) && ( y . Size > 100 )) ? "Большой бум!" : CollideWith ( x как динамический , y как динамический ); частная статическая строка CollideWith ( Asteroid x , Asteroid y ) => "a / a" ; частная статическая строка CollideWith ( Asteroid x , Spaceship y ) => "a / s" ; частная статическая строка CollideWith ( Spaceship x , Asteroid y ) => "s / a" ; частная статическая строка CollideWith ( Spaceship x , Spaceship y ) => "s / s" ; }абстрактный класс SpaceObject { общедоступный SpaceObject ( int size ) => Size = size ; общедоступный int Size { получить ; } }class Asteroid : SpaceObject { public Asteroid ( int size ) : base ( size ) { } }class Spaceship : SpaceObject { public Spaceship ( int size ) : base ( size ) { } }
Выход:
большая стрелав видеSS
Groovy
Groovy - это Java- совместимый / взаимозаменяемый язык JVM общего назначения , который, в отличие от Java, использует позднее связывание / множественную отправку. [12]
/ * Отличная реализация приведенного выше примера C # Позднее связывание работает одинаково при использовании нестатических методов или статической компиляции классов / методов (аннотация @CompileStatic) * / class Program { static void main ( String [] args ) { println Collider . collide ( новый астероид ( 101 ), новый космический корабль ( 300 )) println Collider . collide ( новый астероид ( 10 ), новый космический корабль ( 10 )) println Collider . столкнуться ( новый космический корабль ( 101 ), новый космический корабль ( 10 )) } }class Collider { static String collide ( SpaceObject x , SpaceObject y ) { ( x . size > 100 && y . size > 100 ) ? "big-boom" : collideWith ( x , y ) // Динамическая отправка для метода collideWith } private static String collideWith ( Asteroid x , Asteroid y ) { "a / a" } частный статический String collideWith ( Asteroid x , Spaceship y ) { "a / s" } частный статический String collideWith ( Spaceship x , Asteroid y ) { "s / a " } частная статическая строка collideWith ( Космический корабль x , Космический корабль y ) { " s / s " } }class SpaceObject { int size SpaceObject ( int size ) { this . size = size } }@InheritConstructors класс Asteroid расширяет SpaceObject {} @InheritConstructors класс Spaceship расширяет SpaceObject {}
Common Lisp
На языке с множественной диспетчеризацией, таком как Common Lisp , это может выглядеть примерно так (показан пример Common Lisp):
( defmethod collide-with (( x asteroid ) ( y asteroid )) ;; иметь дело с астероидом, поражающим астероид ) ( defmethod collide-with (( x asteroid ) ( y космический корабль )) ;; иметь дело с астероидом, поражающим космический корабль ) ( defmethod collide- with (( x космический корабль ) ( y астероид )) ;; иметь дело с космическим кораблем, поражающим астероид ) ( defmethod collide-with (( x космический корабль ) ( y космический корабль )) ;; иметь дело с космическим кораблем, поражающим космический корабль )
и аналогично для других методов. Явное тестирование и «динамическое приведение» не используются.
При наличии множественной диспетчеризации традиционная идея о методах, определяемых в классах и содержащихся в объектах, становится менее привлекательной - каждый вышеописанный метод коллизии прикреплен к двум различным классам, а не к одному. Следовательно, специальный синтаксис для вызова метода обычно исчезает, поэтому вызов метода выглядит точно так же, как вызов обычной функции, а методы группируются не в классы, а в универсальные функции .
Юлия
У Джулии есть встроенная множественная диспетчеризация, и это центральное место в языковом дизайне. [3] Версия Джулии из приведенного выше примера может выглядеть так:
collide_with ( x :: Asteroid , y :: Asteroid ) = ... # обработать астероид, поражающий астероид collide_with ( x :: Asteroid , y :: Spaceship ) = ... # обработать астероид, поражающий космический корабль collide_with ( x :: Spaceship , y :: Asteroid ) = ... # обработать столкновение космического корабля с астероидом collide_with ( x :: Spaceship , y :: Spaceship ) = ... # обработать столкновение космического корабля с космическим кораблем
Оболочка нового поколения
В оболочку следующего поколения встроена множественная отправка и отправка предикатов, и они играют центральную роль в разработке языка. [13]
Методы с одинаковыми именами образуют метод множественной отправки, поэтому специального объявления не требуется.
При вызове метода множественной отправки поиск метода-кандидата выполняется снизу вверх. Когда типы аргументов соответствуют типам, указанным для параметров, вызывается метод. Это контрастирует со многими другими языками, где побеждает наиболее специфичное для типа совпадение. Внутри вызываемого метода отказавший защитник (где условие защиты оценивается как ложное) вызывает продолжение поиска метода, вызываемого.
{ type SpaceObject type Asteroid ( SpaceObject ) type Spaceship ( SpaceObject ) }F init ( o : SpaceObject , размер : Int ) o . size = размерF collide ( x : астероид , y : астероид ) "a / a" F столкновение ( x : астероид , y : космический корабль ) "a / s" F столкновение ( x : космический корабль , y : астероид ) "s / a" F столкновение ( x : космический корабль , y : космический корабль ) "s / s"F collide ( x : SpaceObject , y : SpaceObject ) { охранник x . размер > 100 охранник y . size > 100 "big-boom" }эхо ( столкновение ( Астероид ( 101 ), Космический корабль ( 300 ))) эхо ( столкновение ( Астероид ( 10 ), Космический корабль ( 10 )))
Выход:
большая стрелав виде
Раку
Raku , как и Perl, использует проверенные идеи из других языков, а системы типов зарекомендовали себя, предлагая убедительные преимущества в анализе кода на стороне компилятора и мощную семантику на стороне пользователя за счет множественной диспетчеризации.
В нем есть как мультиметоды, так и мультисабы. Поскольку большинство операторов являются подпрограммами, у него также есть несколько отправленных операторов.
Наряду с обычными ограничениями типов, он также имеет ограничения where, которые позволяют создавать очень специализированные подпрограммы.
подмножество Mass of Real, где 0 ^ .. ^ Inf ; Роль Stellar-Object { имеет Mass $ .mass это требуется ; метод name () возвращает Str {...};}class Asteroid делает Stellar-Object { имя метода () { 'астероид' }}class Spaceship делает Stellar-Object { имеет Str $ .name = 'какой-то безымянный космический корабль' ;}мой Str @destroyed = < уничтоженный уничтоженный искореженный >;my Str @damaged = « поврежден » столкнулся с «был поврежден »;# Мы добавляем несколько кандидатов к операторам числового сравнения, потому что сравниваем их численно, # но нет смысла приводить объекты к числовому типу. # (Если бы они выполняли принуждение, нам не обязательно было бы добавлять эти операторы.) # Таким же образом мы могли бы определить совершенно новые операторы. нескольких суб инфикс: «<=>» ( Stellar-объекта: D $ а , Stellar-Object: D $ б ) { $ а . масса <=> $ b . mass } multi суб- инфикс: «<» ( Звездный объект: D $ a , Звездный объект: D $ b ) { $ a . масса < $ b . mass } multi суб- инфикс: «>» ( Звездный объект: D $ a , Звездный объект: D $ b ) { $ a . масса > $ b . mass } мульти суб- инфикс: «==» ( Звездный объект: D $ a , Звездный объект: D $ b ) { $ a . масса == $ b . масса }# Определить новый мульти-диспетчер и добавить некоторые ограничения типа к параметрам. # Если бы мы не определили его, мы получили бы общий, не имеющий ограничений. proto sub collide ( Звездный объект: D $, Звездный объект: D $) {*}# Здесь нет необходимости повторять типы, поскольку они такие же, как и у прототипа. # Ограничение where технически применяется только к $ b, а не ко всей подписи. # Обратите внимание, что ограничение 'where' использует кандидат оператора `<`, который мы добавили ранее. multi sub collide ( $ a , $ b, где $ a < $ b ) { скажем, "$ a.name () было @ destroy.pick () by $ b.name ()" ;}multi sub collide ( $ a , $ b, где $ a > $ b ) { # повторная отправка предыдущему кандидату с заменой аргументов так же, как и $ b , $ a ;}# Это должно быть после первых двух, потому что другие # имеют ограничения 'where', которые проверяются # в том порядке, в котором были написаны подпрограммы. (Этот всегда будет совпадать.) Multi sub collide ( $ a , $ b ) { # рандомизировать порядок my ( $ n1 , $ n2 ) = ( $ a . Name , $ b . Name ). выбрать (*); скажите "$ n1 @ Damaged.pick () $ n2" ;}# Следующие два кандидата могут быть где угодно после прототипа, # потому что они имеют более специализированные типы, чем предыдущие три.# Если корабли имеют неравную массу, вместо них вызывается один из первых двух кандидатов. multi sub collide ( Космический корабль $ a , Космический корабль $ b, где $ a == $ b ) { my ( $ n1 , $ n2 ) = ( $ a . имя , $ b . имя ). выбрать (*); скажем "$ n1 столкнулся с $ n2, и оба корабля были" , ( @destroyed . pick , 'оставлено поврежденным' ). выбрать ;}# Вы можете распаковать атрибуты в переменные внутри подписи. # У вас может быть даже ограничение на них `(: mass ($ a) where 10)`. multi sub collide ( Asteroid $ (: mass ( $ a )), Asteroid $ (: mass ( $ b ))) { скажем, «два астероида столкнулись и объединились в один более крупный астероид с массой {$ a + $ b}» ;}мой космический корабль $ Enterprise . = new (: mass ( 1 ) ,: name ( 'The Enterprise' ));столкнуться с астероидом . новый (: масса ( .1 )), $ Enterprise ;столкнуться $ Enterprise , Космический корабль . новый (: масса ( .1 ));столкнуться $ Enterprise , Asteroid . новый (: масса ( 1 ));столкнуться $ Enterprise , Космический корабль . новый (: масса ( 1 ));столкнуться с астероидом . новый (: масса ( 10 )), Астероид . новый (: масса ( 5 ));
Расширение языков с помощью библиотек с множественной отправкой
JavaScript
В языках, которые не поддерживают множественную отправку на уровне определения языка или на синтаксическом уровне, часто можно добавить множественную отправку с помощью расширения библиотеки . JavaScript и TypeScript не поддерживают мультиметоды на уровне синтаксиса, но можно добавить множественную отправку через библиотеку. Например, пакет multimethod [14] обеспечивает реализацию общих функций множественной диспетчеризации.
Версия с динамической типизацией в JavaScript:
импортировать { мульти , метод } из '@ arrow / multimethod'class Asteroid {} class Spaceship {}const collideWith = multi ( method ([ Asteroid , Asteroid ], ( x , y ) => { // дело с астероидом, поражающим астероид }), method ([ Asteroid , Spaceship ], ( x , y ) => { // дело при столкновении с астероидом космического корабля }), method ([ Spaceship , Asteroid ], ( x , y ) => { // иметь дело с космическим кораблем, поражающим астероид }), method ([ Spaceship , Spaceship ], ( x , y ) => { / / разобраться с космическим кораблем, врезавшимся в космический корабль }), )
Статически типизированная версия в TypeScript:
импортировать { multi , method , Multi } из '@ arrow / multimethod'class Asteroid {} class Spaceship {}type CollideWith = Multi & { ( x : Asteroid , y : Asteroid ) : void ( x : Asteroid , y : Spaceship ) : void ( x : Spaceship , y : Asteroid ) : void ( x : Spaceship , y : Spaceship ) : void }const collideWith : CollideWith = multi ( method ([ Asteroid , Asteroid ], ( x , y ) => { // обрабатываем астероид, падающий на астероид }), method ([ Asteroid , Spaceship ], ( x , y ) => { / / справиться с столкновением с астероидом космическим кораблем }), method ([ Spaceship , Asteroid ], ( x , y ) => { // справиться с столкновением космического корабля с астероидом }), method ([ Spaceship , Spaceship ], ( x , y ) => { // дело с космическим кораблем, поражающим космический корабль }), )
Python
В Python можно добавить множественную отправку с помощью расширения библиотеки . Например, с помощью модуля multimethod.py [15], а также с модулем multimethods.py [16], который предоставляет мультиметоды в стиле CLOS для Python без изменения базового синтаксиса или ключевых слов языка.
от мультиметоды импорта Отправка из game_objects импортировать астероид , космический корабль из game_behaviors импорта as_func , ss_func , sa_func Collide = отправка () Collide . add_rule (( Астероид , Космический корабль ), as_func ) столкнуться . add_rule (( Космический корабль , Космический корабль ), ss_func ) столкнуться . add_rule (( Spaceship , Asteroid ), sa_func ) def aa_func ( a , b ): "" "Поведение при столкновении астероида с астероидом." "" # ... определить новое поведение ... столкновение . add_rule (( Астероид , Астероид ), aa_func )
# ... позже ... столкнуться ( вещь1 , вещь2 )
Функционально это очень похоже на пример CLOS, но синтаксис - обычный Python.
Используя декораторы Python 2.4 , Гвидо ван Россум создал образец реализации мультиметодов [17] с упрощенным синтаксисом:
@multimethod ( Asteroid , Asteroid ) def collide ( a , b ): "" "Поведение при столкновении астероида с астероидом." "" # ... определить новое поведение ... @multimethod ( Asteroid , Spaceship ) def collide ( a , b ): "" "Поведение при столкновении астероида с космическим кораблем." "" # ... определить новое поведение ... # ... определить другие правила для нескольких методов ...
а затем переходит к определению мультиметодного декоратора.
Пакет PEAK-Rules обеспечивает множественную отправку с синтаксисом, аналогичным приведенному выше примеру. [18] Позже он был заменен PyProtocols. [19]
Библиотека Reg также поддерживает множественную и предикатную отправку. [20]
Эмуляция множественной отправки
C
В C нет динамической диспетчеризации, поэтому ее необходимо реализовать вручную в той или иной форме. Часто перечисление используется для идентификации подтипа объекта. Динамическая отправка может быть выполнена путем поиска этого значения в таблице ветвей указателя на функцию . Вот простой пример на C:
typedef void ( * CollisionCase ) ( void );void collision_AA ( void ) { / * обработать столкновение астероид-астероид * / }; void collision_AS ( void ) { / * обработать столкновение астероид и космический корабль * / }; void collision_SA ( void ) { / * обрабатывать столкновение космического корабля и астероида * / }; void collision_SS ( void ) { / * обрабатывать столкновение космического корабля с космическим кораблем * / };typedef enum { THING_ASTEROID = 0 , THING_SPACESHIP , THING_COUNT / * не тип самой вещи, вместо этого используется для поиска количества вещей * / } Thing ;CollisionCase collisionCase [ THING_COUNT ] [ THING_COUNT ] = { { & collision_AA , & collision_AS }, { & collision_SA , & collision_SS } };void collide ( Thing a , Thing b ) { ( * collisionCases [ a ] [ b ]) (); }int main ( void ) { столкновение ( THING_SPACESHIP , THING_ASTEROID ); }
В библиотеке C Object System [21] C действительно поддерживает динамическую диспетчеризацию, аналогичную CLOS. Он полностью расширяемый и не требует ручной обработки методов. Динамические сообщения (методы) отправляются диспетчером COS, который быстрее, чем Objective-C. Вот пример в COS:
#include #include #include // классыdefclass ( Asteroid ) // члены данных endclassdefclass ( Spaceship ) // члены данных endclass// дженерикиDEFGENERIC ( _Bool , collide_with , _1 , _2 );// мультиметодыDEFMETHOD ( _Bool , collide_with , астероид , астероид ) // сделка с астероидом ударяя астероид endmethodDEFMETHOD ( _Bool , collide_with , астероид , космический корабль ) // сделка с астероидом ударного космического корабля endmethodDEFMETHOD ( _Bool , collide_with , Spaceship , Астероид ) // Сделка с космическим кораблем удара астероида endmethodDEFMETHOD ( _Bool , collide_with , Spaceship , Spaceship ) // Сделка с космическим кораблем ударяя космический корабль endmethod// пример использованияint main ( void ) { OBJ a = gnew ( астероид ); OBJ s = gnew ( космический корабль ); printf ( " =% d ,>\ n " , collide_with ( a , a )); printf ( " =% d ,>\ n " , collide_with ( a , s )); printf ( " =% d ,>\ n " , collide_with ( s , a )); printf ( " =% d ,>\ n " , collide_with ( s , s )); grelease ( а ); grelease ( s ); }
C ++
По состоянию на 2021 г.[Обновить], C ++ изначально поддерживает только одиночную отправку, хотя добавление нескольких методов (множественная отправка) было предложено Бьярном Страуструпом (и соавторами) в 2007 году. [22] Методы обхода этого ограничения аналогичны: используйте либо шаблон посетителя , либо динамическое приведение. или библиотека:
// Пример использования сравнения типов во время выполнения через dynamic_cast struct Thing { виртуальная пустота collideWith ( Thing & other ) = 0 ; }; struct Asteroid : Thing { void collideWith ( Thing & other ) { // dynamic_cast к типу указателя возвращает NULL, если приведение завершается неудачно // (dynamic_cast к ссылочному типу вызовет исключение в случае сбоя) if ( auto asteroid = dynamic_cast < Asteroid * > ( и другое )) { // обрабатываем столкновение астероид-астероид } else if ( auto spacehip = dynamic_cast < Spaceship *> ( & other )) { // обрабатываем столкновение астероид-космический корабль } else { // здесь обработка столкновений по умолчанию } } }; struct Spaceship : Thing { void collideWith ( Thing & other ) { if ( auto asteroid = dynamic_cast < Asteroid *> ( & other )) { // обрабатываем столкновение космический корабль-астероид } else if ( auto spacehip = dynamic_cast < Spaceship *> ( & other )) { // обрабатываем столкновение космический корабль-космический корабль } else { // здесь обработка столкновений по умолчанию } } };
или таблица поиска указателя на метод:
#include #include #include <неупорядоченная_карта>class Thing { protected : Thing ( std :: uint32_t cid ) : tid ( cid ) {} const std :: uint32_t tid ; // тип id typedef void ( Вещь :: * Обработчик столкновений ) ( Вещь и другие ); typedef std :: unordered_map < std :: uint64_t , CollisionHandler > CollisionHandlerMap ; static void addHandler ( std :: uint32_t id1 , std :: uint32_t id2 , обработчик CollisionHandler ) { collisionCases . вставить ( CollisionHandlerMap :: value_type ( ключ ( id1 , id2 ), обработчик )); } статический ключ std :: uint64_t ( std :: uint32_t id1 , std :: uint32_t id2 ) { return std :: uint64_t ( id1 ) << 32 | id2 ; } static CollisionHandlerMap collisionCases ; public : void collideWith ( Thing & other ) { auto handler = collisionCases . find ( key ( tid , other . tid )); if ( обработчик ! = collisionCases . end ()) { ( это -> * обработчик -> второй ) ( другое ); // вызов указателя на метод } else { // обработка столкновений по умолчанию } } };class Asteroid : public Thing { void asteroid_collision ( Thing & other ) { / * обрабатывать столкновение астероид-астероид * / } void spacehip_collision ( Thing & other ) { / * обрабатывать столкновение астероид-космический корабль * / } public : Asteroid () : Thing ( cid ) {} static void initCases (); статическая константа std :: uint32_t cid ; };class Spaceship : public Thing { void asteroid_collision ( Thing & other ) { / * обрабатывать столкновение космического корабля с астероидом * / } void spacehip_collision ( Thing & other ) { / * обрабатывать столкновение космического корабля-космического корабля * / } public : Spaceship () : Thing ( cid ) {} static void initCases (); статическая константа std :: uint32_t cid ; // идентификатор класса };Thing :: CollisionHandlerMap Thing :: collisionCases ; const std :: uint32_t Астероид :: cid = typeid ( Астероид ). hash_code (); const std :: uint32_t Космический корабль :: cid = typeid ( Космический корабль ). hash_code ();void Asteroid :: initCases () { addHandler ( cid , cid , CollisionHandler ( & Asteroid :: asteroid_collision )); addHandler ( cid , Spaceship :: cid , CollisionHandler ( & Asteroid :: spacehip_collision )); }void Spaceship :: initCases () { addHandler ( cid , Asteroid :: cid , CollisionHandler ( & Spaceship :: asteroid_collision )); addHandler ( cid , cid , CollisionHandler ( & Spaceship :: spacehip_collision )); }int main () { Астероид :: initCases (); Космический корабль :: initCases (); Астероид а1 , а2 ; Космический корабль s1 , s2 ; а1 . collideWith ( a2 ); а1 . collideWith ( s1 ); s1 . collideWith ( s2 ); s1 . collideWith ( a1 ); }
Yomm2 библиотека [23] обеспечивает быстрый, ортогональной реализацию открытых мультиметодов.
Синтаксис объявления открытых методов основан на предложении о собственной реализации C ++. Библиотека требует, чтобы пользователь регистрировал все классы, используемые в качестве виртуальных аргументов (и их подклассы), но не требует каких-либо изменений существующего кода. Методы реализованы как обычные встроенные функции C ++; они могут быть перегружены и могут передаваться по указателю. Нет ограничений на количество виртуальных аргументов, и их можно произвольно смешивать с невиртуальными аргументами.
Библиотека использует комбинацию методов (сжатые таблицы диспетчеризации, идеальный целочисленный хэш) для реализации вызовов методов в постоянное время, уменьшая при этом использование памяти. Отправка вызова открытого метода с одним виртуальным аргументом занимает всего на 15–30% больше времени, чем вызов обычной виртуальной функции-члена, когда используется современный оптимизирующий компилятор.
Пример Asteroids можно реализовать следующим образом:
# включить используя yorel :: yomm2 :: virtual_ ;class Thing { public : virtual ~ Thing () {} // ... };class Asteroid : public Thing { // ... };class Spaceship : public Thing { // ... };register_class ( Вещь ); register_class ( Космический корабль , Вещь ); register_class ( Астероид , Вещь );declare_method ( void , collideWith , ( виртуальная_ < вещь &> , виртуальная_ < вещь &> ));define_method ( void , collideWith , ( Thing & left , Thing & right )) { // обработка столкновений по умолчанию }define_method ( void , collideWith , ( Asteroid & left , Asteroid & right )) { // обрабатываем столкновение астероид-астероид }define_method ( void , collideWith , ( Asteroid & left , Spaceship & right )) { // обрабатываем столкновение астероид-космический корабль }define_method ( void , collideWith , ( Spaceship & left , Asteroid & right )) { // обрабатываем столкновение Spaceship-Asteroid }define_method ( void , collideWith , ( Spaceship & left , Spaceship & right )) { // обрабатываем столкновение космического корабля-космического корабля }int main () { yorel :: yomm2 :: update_methods (); Астероид а1 , а2 ; Космический корабль s1 , s2 ; collideWith ( a1 , a2 ); collideWith ( a1 , s1 ); collideWith ( s1 , s2 ); collideWith ( s1 , a1 ); }
Страуструп упоминает в The Design and Evolution of C ++, что ему понравилась концепция мультиметодов и он рассматривал возможность ее реализации на C ++, но утверждает, что не смог найти эффективную реализацию примера (сравнимую с виртуальными функциями) и решить некоторые возможные проблемы неоднозначности типов. Затем он заявляет, что, хотя эта функция по-прежнему была бы хороша, ее можно приблизительно реализовать с помощью двойной диспетчеризации или таблицы поиска на основе типов, как указано в примере C / C ++ выше, поэтому это функция с низким приоритетом для будущих версий языка. [24]
D
По состоянию на 2021 г.[Обновить], как и многие другие объектно-ориентированные языки программирования, D изначально поддерживает только однократную отправку. Тем не менее, можно имитировать открытые мультиметоды как библиотеки функции в D. openmethods библиотеки [25] является примером.
// Объявление Matrix plus ( virtual ! Matrix , virtual ! Matrix );// Переопределение для двух объектов DenseMatrix @method Matrix _plus ( DenseMatrix a , DenseMatrix b ) { const int nr = a . ряды ; const int nc = а . cols ; assert ( a . nr == b . nr ); assert ( a . nc == b . nc ); автоматический результат = новый DenseMatrix ; результат . nr = nr ; результат . nc = nc ; результат . элем . длина = а . элем . длина ; результат . elems [] = а . elems [] + b . elems []; вернуть результат ; }// Переопределение для двух объектов DiagonalMatrix @method Matrix _plus ( DiagonalMatrix a , DiagonalMatrix b ) { assert ( a . Rows == b . Rows ); двойная [] сумма ; сумма . длина = а . элем . длина ; сумма [] = а . elems [] + b . elems []; вернуть новый DiagonalMatrix ( sum ); }
Ява
В языке с единственной отправкой, таком как Java , множественная отправка может быть эмулирована с несколькими уровнями единой отправки:
интерфейс Collideable { void collideWith ( final Collideable other ); / * Этим методам потребуются разные имена на языке без перегрузки методов. * / void collideWith ( последний астероид астероида ); void collideWith ( последний космический корабль космического корабля ); }class Asteroid реализует Collideable { public void collideWith ( final Collideable other ) { // Вызов collideWith для другого объекта. другое . collideWith ( это ); } public void collideWith ( final Asteroid asteroid ) { // Обработка столкновения астероид-астероид. } public void collideWith ( последний космический корабль космического корабля ) { // Обработка столкновения астероида и космического корабля. } }class Spaceship реализует Collideable { public void collideWith ( final Collideable other ) { // Вызов collideWith для другого объекта. другое . collideWith ( это ); } public void collideWith ( последний астероид астероида ) { // Обработка столкновения космического корабля и астероида. } public void collideWith ( последний космический корабль космический корабль ) { // Обработка столкновения космического корабля-космического корабля. } }
instanceof
Также можно использовать проверки времени выполнения на одном или обоих уровнях.
Поддержка языков программирования
Первичная парадигма
- Юлия [26]
Поддержка общих мультиметодов
- C # 4.0 [27]
- Сесил [28]
- Clojure [29]
- Common Lisp (через объектную систему Common Lisp ) [30]
- Дилан [31]
- Эликсир [32]
- Крепость [33]
- Отличный [34]
- Лассо [35] [36]
- Nim , до v0.19.x (несколько методов устарели в v0.20) [37]
- Раку [38]
- R [39]
- Seed7 [40]
- TADS [41]
- VB.Net [42] через позднее связывание, также через .Net DLR [43]
- Wolfram Language [44] через символьное сопоставление с образцом
- Xtend [45]
Через расширения
- Любой язык .NET (через библиотеку MultiMethods.NET )
- C (через библиотеку C Object System )
- C # (через библиотеку multimethod-sharp )
- C ++ (через библиотеку yomm2 и мультиметоды )
- D (через библиотеку openmethods )
- Фактор (через стандартный словарь мультиметодов )
- Java (с использованием расширения MultiJava )
- JavaScript (через пакет @ arrow / multimethod )
- Perl (через модуль Class :: Multimethods )
- Python (через PEAK-Rules , RuleDispatch , gnosis.magic.multimethods , PyMultimethods или множественную рассылку )
- Ракетка (через multimethod-lib )
- Рубин (через библиотеку Множественная диспетчерская Библиотека и Многометодная пакет и VLX-Мультиметоды пакет )
- Схема (например, через TinyCLOS )
- TypeScript (через пакет @ arrow / multimethod )
Смотрите также
- Предикатная отправка
Рекомендации
- ^ Ранка, Санджай; Банерджи, Арунава; Бисвас, Канад Кишор; Дуа, Сумит; Мишра, Прабхат; Муна, Раджат (26.07.2010). Современные вычисления: Вторая международная конференция, IC3 2010, Нойда, Индия, 9–11 августа 2010 г. Труды . Springer. ISBN 9783642148248.
- ^ Б с д е е г ч я J K Мушевичи, Раду; Потанин Алексей; Темперо, Эван; Благородный, Джеймс (2008). Множественная отправка на практике . Материалы 23-й конференции ACM SIGPLAN по языкам и приложениям систем объектно-ориентированного программирования . ОПСЛА '08. Нэшвилл, Теннесси, США: ACM. С. 563–582. DOI : 10.1145 / 1449764.1449808 . ISBN 9781605582153. S2CID 7605233 .
- ^ а б в г д Безансон, Джефф; Эдельман, Алан; Карпинский, Стефан; Шах, Вирал Б. (7 февраля 2017 г.). «Юлия: свежий подход к численным вычислениям». SIAM Обзор . 59 (1): 65–98. arXiv : 1411.1607 . DOI : 10.1137 / 141000671 . S2CID 13026838 .
- ^ Кастанья, Джузеппе; Гелли, Джорджио и Лонго, Джузеппе (1995). «Исчисление перегруженных функций с подтипами» . Информация и вычисления . 117 (1): 115–135. DOI : 10.1006 / inco.1995.1033 . Проверено 19 апреля 2013 .
- ^ Кастанья, Джузеппе (1996). Объектно-ориентированное программирование: единый фундамент . Успехи теоретической информатики. Birkhäuser. п. 384. ISBN 978-0-8176-3905-1.
- ^ Кастанья, Джузеппе (1995). «Ковариация и контравариантность: конфликт без причины». Транзакции ACM по языкам и системам программирования . 17 (3): 431–447. CiteSeerX 10.1.1.115.5992 . DOI : 10.1145 / 203095.203096 . S2CID 15402223 .
- ^ Брюс, Ким; Карделли, Лука; Кастанья, Джузеппе; Ливенс, Гэри Т .; Пирс, Бенджамин (1995). «О бинарных методах» . Теория и практика объектных систем . 1 (3): 221–242. DOI : 10.1002 / j.1096-9942.1995.tb00019.x . Проверено 19 апреля 2013 .
- ^ «Использование динамического типа (Руководство по программированию на C #)» . Проверено 14 мая 2020 .
- ^ "выражение переключателя (справочник по C #)" . Проверено 14 мая 2020 .
- ^ «Основные понятия» . Проверено 14 мая 2020 .
- ^ «Dynamic .NET - понимание динамического ключевого слова в C # 4» . Проверено 14 мая 2020 .
- ^ Groovy - Мульти-методы
- ^ «Руководство пользователя NGSLANG (1) NGS» . ngs-lang.org . Проверено 1 октября 2019 .
- ^ @ arrow / multimethod Множественная отправка в JavaScript / TypeScript с настраиваемым разрешением отправки, разработанным Maciej Cąderek.
- ^ Coady, Aric, multimethod: диспетчеризация с несколькими аргументами. , получено 2021-01-28
- ^ multimethods.py Архивировано 9 марта2005 г. на Wayback Machine , Множественная отправка на Python с настраиваемым разрешением отправки, Дэвид Мертц и др.
- ^ «Пятиминутные мультиметоды на Python» .
- ^ «ПИК-Правила 0.5a1.dev» . Индекс пакетов Python . Проверено 21 марта 2014 года .
- ^ «PyProtocols» . Комплект приложений Python Enterprise . Проверено 26 апреля 2019 .
- ^ «Рег» . Прочтите документацию . Проверено 26 апреля 2019 .
- ^ «C Object System: структура, которая выводит C на уровень других языков программирования высокого уровня и выше: CObjectSystem / COS» . 2019-02-19.
- ^ «Отчет о языковой поддержке мульти-методов и открытых методов для C ++» (PDF) . 2007-03-11.
Множественная отправка - выбор вызываемой функции на основе динамического типа двух или более аргументов - это решение нескольких классических проблем объектно-ориентированного программирования.
- ^ yomm2 , Быстрые, ортогональные открытые мульти-методы для C ++, Жан-Луи Леруа.
- ^ Страуструп, Бьярн (1994). «Раздел 13.8». Дизайн и эволюция C ++ . Индианаполис, Индиана, США: Аддисон Уэсли. Bibcode : 1994dec..book ..... S . ISBN 978-0-201-54330-8.
- ^ openmethods , Открытые мульти-методы для D Жан-Луи Леруа.
- ^ «Методы» . Руководство Джулии . Джулиаланг. Архивировано из оригинала 17 июля 2016 года . Проверено 11 мая 2014 .
- ^ «Мультиметоды в C # 4.0 с« динамическим » » . Проверено 20 августа 2009 .
- ^ «Сесил язык» . Проверено 13 апреля 2008 .
- ^ «Мультиметоды в Clojure» . Проверено 4 сентября 2008 .
- ^ Стил, Гай Л. (1990). «28» . Общий LISP: The Language . Бедфорд, Массачусетс, США: Digital Press. ISBN 978-1-55558-041-4.
- ^ «Предпосылки и цели» . Проверено 13 апреля 2008 .
- ^ "Elixir Lang | Начало работы | Модули и функции" . Проверено 10 ноября 2017 .
- ^ «Спецификация языка Fortress, версия 1.0» (PDF) . Архивировано из оригинального (PDF) 20 января 2013 года . Проверено 23 апреля 2010 .
- ^ «Мультиметоды в Groovy» . Проверено 13 апреля 2008 .
- ^ «Методы - LassoGuide 9.2» . Проверено 11 ноября 2014 .
- ^ «Шаблон посетителя по сравнению с несколькими методами» . Проверено 13 апреля 2008 .
- ^ "Руководство Nim: Множественные методы" . Проверено 11 сентября 2020 .
- ^ «Часто задаваемые вопросы по Perl 6» . Проверено 13 апреля 2008 .
- ^ «Как работают методы S4» (PDF) . Проверено 13 апреля 2008 .
- ^ «Множественная отправка в Seed7» . Проверено 23 апреля 2011 .
- ^ «Системное руководство ТАДС 3» . Проверено 19 марта 2012 .
- ^ "Множественная рассылка VB.Net" . Проверено 31 марта 2020 .
- ^ «Новые возможности в C # 4.0 и VB.Net 10.0» . Проверено 31 марта 2020 .
- ^ «Заметки для знатоков языков программирования» . Проверено 21 августа 2016 .
- ^ «Множественная отправка» .
Внешние ссылки
- Страуструп, Бьярн; Солодкий, Юрий; Пиркельбауэр, Питер (2007). Открытые мульти-методы для C ++ (PDF) . 6-я Международная конференция ACM по генеративному программированию и компонентной инженерии.
- «Динамическая множественная рассылка» . docs.racket-lang.org . Проверено 12 марта 2018 .