Jump to content

Множественная отправка

(Перенаправлено с Мультиметоды )

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

Понимание отправки

[ редактировать ]

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

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

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

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

Объектная система Common Lisp (CLOS) — ранний и хорошо известный пример множественной диспетчеризации. Еще одним ярким примером использования множественной диспетчеризации является язык программирования Julia .

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

Типы данных

[ редактировать ]

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

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

Динамическая диспетчеризация вызовов функций связана с некоторыми затратами во время выполнения. В некоторых языках [ нужна ссылка ] Различие между перегрузкой и мультиметодами может быть размытым, поскольку компилятор определяет, можно ли применить выбор времени компиляции к данному вызову функции или требуется более медленная диспетчеризация во время выполнения.

Проблемы

[ редактировать ]

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

Выразительность и модульность

[ редактировать ]

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

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

  • Можно определить разные «случаи» мультиметода из разных пакетов без изменения исходного кода базового пакета.
  • Включение другого пакета в программу не должно изменять поведение данного вызова нескольких методов, если вызов не использует какие-либо типы данных, определенные в пакете.
  • И наоборот, если тип данных определен в данном пакете, а многометодное расширение, использующее этот тип, также определено в том же пакете, и значение этого типа передается (через ссылку на базовый тип или в универсальную функцию) в другой пакет, не зависящий от этого пакета, а затем вызывается мультиметод с этим значением в качестве аргумента, следует использовать случай с несколькими методами, определенный в пакете, который включает этот тип. Другими словами, в рамках данной программы один и тот же мультиметод, вызываемый с одним и тем же набором аргументов, должен разрешаться к одной и той же реализации, независимо от местоположения места вызова и от того, находится ли данное определение «в области видимости». " или "видимый" в момент вызова метода.

Двусмысленность

[ редактировать ]

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

Возможные решения включают в себя:

  • Рассматривать любые неоднозначные вызовы как ошибку. Это может быть обнаружено во время компиляции (или иным образом перед развертыванием), но может не быть обнаружено до времени выполнения и вызвать ошибку времени выполнения.
  • Упорядочение аргументов, например, выбирается случай с наиболее конкретным первым аргументом, а последующие аргументы не учитываются для разрешения неоднозначности, если первый аргумент недостаточен для решения проблемы.
  • Построение других правил разрешения неоднозначности в ту или иную сторону. Иногда такие правила могут быть произвольными и неожиданными. Например, в правилах статического разрешения перегрузки в C++ тип, который точно соответствует, по понятным причинам считается лучшим соответствием, чем тип, который соответствует через ссылку на базовый тип или универсальный параметр (шаблон). Однако если единственные возможные совпадения происходят либо через базовый тип, либо через универсальный параметр, универсальный параметр предпочтительнее базового типа, и это правило иногда приводит к неожиданному поведению.

Эффективность

[ редактировать ]

Эффективная реализация единой диспетчеризации, в том числе на языках программирования, которые отдельно компилируются в объектный код и связываются с низкоуровневым (не поддерживающим язык) компоновщиком, в том числе динамически во время загрузки/запуска программы или даже под управлением приложения. код, хорошо известны. Метод " vtable ", разработанный на C++ и других ранних объектно-ориентированных языках (где каждый класс имеет массив указателей функций, соответствующих виртуальным функциям этого класса), почти так же быстр, как вызов статического метода, требуя O(1) накладных расходов и только одного дополнительного поиск в памяти даже в неоптимизированном случае. Однако метод vtable использует имя функции, а не тип аргумента, в качестве ключа поиска и не масштабируется для случая множественной отправки. (Это также зависит от объектно-ориентированной парадигмы методов, которые являются функциями классов, а не отдельными объектами, независимыми от какого-либо конкретного типа данных).

Эффективная реализация множественной диспетчеризации остается постоянной исследовательской проблемой.

Использование на практике

[ редактировать ]

Чтобы оценить, как часто на практике используется множественная диспетчеризация, 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 , где множественная диспетчеризация была центральной концепцией дизайна с самого начала языка: собрав ту же статистику, что и Мушевичи, о среднем количестве методов на одну обобщенную функцию, было обнаружено, что стандартная библиотека Julia использует количество перегрузок более чем в два раза больше, чем в других языках, проанализированных Мушевичем, и более чем в 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 ( Стальной банк ) [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
МультиЯва [2] 1.50 8.92 1.02
Хороший [2] 1.36 3.46 0.33

Теория множественных языков диспетчеризации была впервые разработана Кастанья и др. путем определения модели перегруженных функций с поздним связыванием . [4] [5] Это привело к первой формализации проблемы ковариантности и контравариантности объектно-ориентированных языков. [6] и решение проблемы бинарных методов. [7]

Различие многократной и однократной отправки можно прояснить на примере. Представьте себе игру, в которой среди (видимых пользователем) объектов есть космические корабли и астероиды. Когда два объекта сталкиваются, программе может потребоваться выполнить разные действия в зависимости от того, что с чем только что столкнулось.

Языки со встроенной множественной отправкой

[ редактировать ]

В C# появилась поддержка динамических мультиметодов в версии 4. [8] (апрель 2010 г.) с использованием ключевого слова «динамический». В следующем примере демонстрируются мультиметоды. Как и многие другие статически типизированные языки, C# также поддерживает перегрузку статических методов. [9] Microsoft ожидает, что в большинстве сценариев разработчики предпочтут статическую типизацию динамической типизации. [10] Ключевое слово Dynamic поддерживает взаимодействие с COM-объектами и динамически типизированными языками .NET.

В приведенном ниже примере используются функции, представленные в C# 9 и C# 10.

using static ColliderLibrary;Console.WriteLine(Collide(new Asteroid(101), new Spaceship(300)));Console.WriteLine(Collide(new Asteroid(10), new Spaceship(10)));Console.WriteLine(Collide(new Spaceship(101), new Spaceship(10)));string Collide(SpaceObject x, SpaceObject y) =>    x.Size > 100 && y.Size > 100        ? "Big boom!"        : CollideWith(x as dynamic, y as dynamic); // Dynamic dispatch to CollideWith methodclass ColliderLibrary{    public static string CollideWith(Asteroid x, Asteroid y) => "a/a";    public static string CollideWith(Asteroid x, Spaceship y) => "a/s";    public static string CollideWith(Spaceship x, Asteroid y) => "s/a";    public static string CollideWith(Spaceship x, Spaceship y) => "s/s";}abstract record SpaceObject(int Size);record Asteroid(int Size) : SpaceObject(Size);record Spaceship(int Size) : SpaceObject(Size);

Выход:

Big boom!a/ss/s

классный

[ редактировать ]

Groovy общего назначения  — это Java- совместимый/взаимоиспользуемый язык JVM , который, в отличие от Java, использует позднее связывание/множественную диспетчеризацию. [11]

/*    Groovy implementation of C# example above    Late binding works the same when using non-static methods or compiling class/methods statically    (@CompileStatic annotation)*/class Program {    static void main(String[] args) {        println Collider.collide(new Asteroid(101), new Spaceship(300))        println Collider.collide(new Asteroid(10), new Spaceship(10))        println Collider.collide(new Spaceship(101), new Spaceship(10))    }}class Collider {    static String collide(SpaceObject x, SpaceObject y) {        (x.size > 100 && y.size > 100) ? "big-boom" : collideWith(x, y)  // Dynamic dispatch to collideWith method    }    private static String collideWith(Asteroid x, Asteroid y) { "a/a" }    private static String collideWith(Asteroid x, Spaceship y) { "a/s" }    private static String collideWith(Spaceship x, Asteroid y) { "s/a" }    private static String collideWith(Spaceship x, Spaceship y) { "s/s"}}class SpaceObject {    int size    SpaceObject(int size) { this.size = size }}@InheritConstructors class Asteroid extends SpaceObject {}@InheritConstructors class Spaceship extends SpaceObject {}

Общий Лисп

[ редактировать ]

В языке с множественной диспетчеризацией, таком как Common Lisp , это может выглядеть примерно так (показан пример Common Lisp):

(defmethod collide-with ((x asteroid) (y asteroid))  ;; deal with asteroid hitting asteroid  )(defmethod collide-with ((x asteroid) (y spaceship))  ;; deal with asteroid hitting spaceship  )(defmethod collide-with ((x spaceship) (y asteroid))  ;; deal with spaceship hitting asteroid  )(defmethod collide-with ((x spaceship) (y spaceship))  ;; deal with spaceship hitting spaceship  )

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

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

В Julia есть встроенная множественная диспетчеризация, и это занимает центральное место в конструкции языка. [3] Версия Джулии приведенного выше примера может выглядеть так:

abstract type SpaceObject endstruct Asteroid <: SpaceObject    size::Int    endstruct Spaceship <: SpaceObject    size::Int                  endcollide_with(::Asteroid, ::Spaceship) = "a/s"collide_with(::Spaceship, ::Asteroid) = "s/a"collide_with(::Spaceship, ::Spaceship) = "s/s"collide_with(::Asteroid, ::Asteroid) = "a/a"collide(x::SpaceObject, y::SpaceObject) = (x.size > 100 && y.size > 100) ? "Big boom!" : collide_with(x, y)

Выход:

julia> collide(Asteroid(101), Spaceship(300))"Big boom!"julia> collide(Asteroid(10), Spaceship(10))"a/s"julia> collide(Spaceship(101), Spaceship(10))"s/s"

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

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

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

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 makes no 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));

Расширение языков с помощью библиотек с несколькими диспетчеризациями

[ редактировать ]

В языках, которые не поддерживают множественную отправку на уровне определения языка или синтаксическом уровне, часто можно добавить множественную отправку с помощью расширения библиотеки . JavaScript и TypeScript не поддерживают мультиметоды на уровне синтаксиса, но можно добавить множественную диспетчеризацию через библиотеку. Например, пакет мультиметодов [12] обеспечивает реализацию нескольких общих функций диспетчеризации.

Динамически типизированная версия на JavaScript:

import { multi, method } from '@arrows/multimethod'class Asteroid {}class Spaceship {}const collideWith = multi(  method([Asteroid, Asteroid], (x, y) => {    // deal with asteroid hitting asteroid  }),  method([Asteroid, Spaceship], (x, y) => {    // deal with asteroid hitting spaceship  }),  method([Spaceship, Asteroid], (x, y) => {    // deal with spaceship hitting asteroid  }),  method([Spaceship, Spaceship], (x, y) => {    // deal with spaceship hitting spaceship  }),)

Статически типизированная версия на TypeScript:

import { multi, method, Multi } from '@arrows/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) => {    // deal with asteroid hitting asteroid  }),  method([Asteroid, Spaceship], (x, y) => {    // deal with asteroid hitting spaceship  }),  method([Spaceship, Asteroid], (x, y) => {    // deal with spaceship hitting asteroid  }),  method([Spaceship, Spaceship], (x, y) => {    // deal with spaceship hitting spaceship  }),)

Множественную диспетчеризацию можно добавить в Python с помощью расширения библиотеки . Например, используя модуль multimethod.py [13] а также с модулем multimethods.py [14] который предоставляет мультиметоды в стиле CLOS для Python без изменения базового синтаксиса или ключевых слов языка.

from multimethods import Dispatchfrom game_objects import Asteroid, Spaceshipfrom game_behaviors import as_func, ss_func, sa_funccollide = Dispatch()collide.add_rule((Asteroid, Spaceship), as_func)collide.add_rule((Spaceship, Spaceship), ss_func)collide.add_rule((Spaceship, Asteroid), sa_func)def aa_func(a, b):    """Behavior when asteroid hits asteroid."""    # ...define new behavior...collide.add_rule((Asteroid, Asteroid), aa_func)
# ...later...collide(thing1, thing2)

Функционально это очень похоже на пример CLOS, но синтаксис обычный Python.

Используя декораторы Python 2.4 , Гвидо ван Россум создал пример реализации мультиметодов. [15] с упрощенным синтаксисом:

@multimethod(Asteroid, Asteroid)def collide(a, b):    """Behavior when asteroid hits a asteroid."""    # ...define new behavior...@multimethod(Asteroid, Spaceship)def collide(a, b):    """Behavior when asteroid hits a spaceship."""    # ...define new behavior...# ... define other multimethod rules ...

а затем определяется декоратор мультиметода.

Пакет PEAK-Rules обеспечивает множественную диспетчеризацию с синтаксисом, аналогичным приведенному выше примеру. [16] Позже он был заменен PyProtocols. [17]

Библиотека Reg также поддерживает множественную отправку и отправку предикатов. [18]

С появлением подсказок типов возможна множественная отправка с еще более простым синтаксисом. Например, используя Plum-dispatch ,

from plum import dispatch@dispatchdef collide(a: Asteroid, b: Asteroid):    """Behavior when asteroid hits a asteroid."""    # ...define new behavior...    @dispatchdef collide(a: Asteroid, b: Spaceship):    """Behavior when asteroid hits a spaceship."""    # ...define new behavior...    # ...define further rules...

Эмуляция множественной отправки

[ редактировать ]

В языке C нет динамической диспетчеризации, поэтому ее необходимо в той или иной форме реализовать вручную. Часто перечисление используется для идентификации подтипа объекта. Динамическую отправку можно выполнить, найдя это значение в указателя функции таблице ветвей . Вот простой пример на C:

typedef void (*CollisionCase)(void);void collision_AA(void) { /* handle Asteroid-Asteroid collision  */ };void collision_AS(void) { /* handle Asteroid-Spaceship collision */ };void collision_SA(void) { /* handle Spaceship-Asteroid collision */ };void collision_SS(void) { /* handle Spaceship-Spaceship collision*/ };typedef enum {    THING_ASTEROID = 0,    THING_SPACESHIP,    THING_COUNT /* not a type of thing itself, instead used to find number of things */} Thing;CollisionCase collisionCases[THING_COUNT][THING_COUNT] = {    {&collision_AA, &collision_AS},    {&collision_SA, &collision_SS}};void collide(Thing a, Thing b) {    (*collisionCases[a][b])();}int main(void) {    collide(THING_SPACESHIP, THING_ASTEROID);}

С помощью библиотеки объектной системы C, [19] C поддерживает динамическую отправку, аналогичную CLOS. Он полностью расширяем и не требует ручной обработки методов. Динамические сообщения (методы) отправляются диспетчером COS, который работает быстрее, чем Objective-C. Вот пример в COS:

#include <stdio.h>#include <cos/Object.h>#include <cos/gen/object.h>// classesdefclass (Asteroid)// data membersendclassdefclass (Spaceship)// data membersendclass// genericsdefgeneric (_Bool, collide_with, _1, _2);// multimethodsdefmethod (_Bool, collide_with, Asteroid, Asteroid) // deal with asteroid hitting asteroidendmethoddefmethod (_Bool, collide_with, Asteroid, Spaceship) // deal with asteroid hitting spaceshipendmethoddefmethod (_Bool, collide_with, Spaceship, Asteroid) // deal with spaceship hitting asteroidendmethoddefmethod (_Bool, collide_with, Spaceship, Spaceship) // deal with spaceship hitting spaceshipendmethod// example of useint main(void){  OBJ a = gnew(Asteroid);  OBJ s = gnew(Spaceship);  printf("<a,a> = %d\n", collide_with(a, a));  printf("<a,s> = %d\n", collide_with(a, s));  printf("<s,a> = %d\n", collide_with(s, a));  printf("<s,s> = %d\n", collide_with(s, s));  grelease(a);  grelease(s);}

По состоянию на 2021 год изначально поддерживает только одиночную отправку , C++ хотя добавление нескольких методов (множественная диспетчеризация) было предложено Бьярном Страуструпом (и его соавторами) в 2007 году. [20] Методы обхода этого ограничения аналогичны: используйте либо шаблон посетителя , динамическое приведение типов, либо библиотеку:

 // Example using run time type comparison via dynamic_cast struct Thing {     virtual void collideWith(Thing& other) = 0; }; struct Asteroid : Thing {     void collideWith(Thing& other) {         // dynamic_cast to a pointer type returns NULL if the cast fails         // (dynamic_cast to a reference type would throw an exception on failure)         if (auto asteroid = dynamic_cast<Asteroid*>(&other)) {             // handle Asteroid-Asteroid collision         } else if (auto spaceship = dynamic_cast<Spaceship*>(&other)) {             // handle Asteroid-Spaceship collision         } else {             // default collision handling here         }     } }; struct Spaceship : Thing {     void collideWith(Thing& other) {         if (auto asteroid = dynamic_cast<Asteroid*>(&other)) {             // handle Spaceship-Asteroid collision         } else if (auto spaceship = dynamic_cast<Spaceship*>(&other)) {             // handle Spaceship-Spaceship collision         } else {             // default collision handling here         }     } };

или таблица поиска указателя на метод:

#include <cstdint>#include <typeinfo>#include <unordered_map>class Thing {  protected:    Thing(std::uint32_t cid) : tid(cid) {}    const std::uint32_t tid; // type id    typedef void (Thing::*CollisionHandler)(Thing& other);    typedef std::unordered_map<std::uint64_t, CollisionHandler> CollisionHandlerMap;    static void addHandler(std::uint32_t id1, std::uint32_t id2, CollisionHandler handler) {        collisionCases.insert(CollisionHandlerMap::value_type(key(id1, id2), handler));    }    static std::uint64_t key(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 (handler != collisionCases.end()) {            (this->*handler->second)(other); // pointer-to-method call        } else {            // default collision handling        }    }};class Asteroid: public Thing {    void asteroid_collision(Thing& other)   { /*handle Asteroid-Asteroid collision*/ }    void spaceship_collision(Thing& other)  { /*handle Asteroid-Spaceship collision*/}  public:    Asteroid(): Thing(cid) {}    static void initCases();    static const std::uint32_t cid;};class Spaceship: public Thing {    void asteroid_collision(Thing& other)   { /*handle Spaceship-Asteroid collision*/}    void spaceship_collision(Thing& other)  { /*handle Spaceship-Spaceship collision*/}  public:    Spaceship(): Thing(cid) {}    static void initCases();    static const std::uint32_t cid; // class id};Thing::CollisionHandlerMap Thing::collisionCases;const std::uint32_t Asteroid::cid = typeid(Asteroid).hash_code();const std::uint32_t Spaceship::cid = typeid(Spaceship).hash_code();void Asteroid::initCases() {    addHandler(cid, cid, CollisionHandler(&Asteroid::asteroid_collision));    addHandler(cid, Spaceship::cid, CollisionHandler(&Asteroid::spaceship_collision));}void Spaceship::initCases() {    addHandler(cid, Asteroid::cid, CollisionHandler(&Spaceship::asteroid_collision));    addHandler(cid, cid, CollisionHandler(&Spaceship::spaceship_collision));}int main() {    Asteroid::initCases();    Spaceship::initCases();    Asteroid  a1, a2;    Spaceship s1, s2;    a1.collideWith(a2);    a1.collideWith(s1);    s1.collideWith(s2);    s1.collideWith(a1);}

Библиотека YOMM2 [21] обеспечивает быструю ортогональную реализацию открытых мультиметодов.

Синтаксис объявления открытых методов основан на предложении собственной реализации C++. Библиотека требует, чтобы пользователь зарегистрировал все классы, используемые в качестве виртуальных аргументов (и их подклассы), но не требует каких-либо изменений в существующем коде. Методы реализованы как обычные встроенные функции C++; они могут быть перегружены и могут передаваться по указателю. Ограничений на количество виртуальных аргументов нет, и их можно произвольно смешивать с невиртуальными аргументами.

Библиотека использует комбинацию методов (сжатые таблицы диспетчеризации, целочисленные хеш-таблицы без коллизий) для реализации вызовов методов за постоянное время, одновременно уменьшая использование памяти. При использовании современного оптимизирующего компилятора диспетчеризация вызова открытого метода с одним виртуальным аргументом занимает всего на 15–30% больше времени, чем вызов обычной виртуальной функции-члена.

Пример астероидов можно реализовать следующим образом:

#include <yorel/yomm2/keywords.hpp>#include <memory>class Thing {  public:    virtual ~Thing() {}};class Asteroid : public Thing {};class Spaceship : public Thing {};register_classes(Thing, Spaceship, Asteroid);declare_method(void, collideWith, (virtual_<Thing&>, virtual_<Thing&>));define_method(void, collideWith, (Thing& left, Thing& right)) {    // default collision handling}define_method(void, collideWith, (Asteroid& left, Asteroid& right)) {    // handle Asteroid-Asteroid collision}define_method(void, collideWith, (Asteroid& left, Spaceship& right)) {    // handle Asteroid-Spaceship collision}define_method(void, collideWith, (Spaceship& left, Asteroid& right)) {    // handle Spaceship-Asteroid collision}define_method(void, collideWith, (Spaceship& left, Spaceship& right)) {    // handle Spaceship-Spaceship collision}int main() {    yorel::yomm2::update_methods();    std::unique_ptr<Thing> a1(std::make_unique<Asteroid>()),        a2(std::make_unique<Asteroid>());    std::unique_ptr<Thing> s1(std::make_unique<Spaceship>()),        s2(std::make_unique<Spaceship>());    // note: types partially erased    collideWith(*a1, *a2); // Asteroid-Asteroid collision    collideWith(*a1, *s1); // Asteroid-Spaceship collision    collideWith(*s1, *a1); // Spaceship-Asteroid collision    collideWith(*s1, *s2); // Spaceship-Spaceship collision    return 0;}

Страуструп упоминает в книге «Дизайн и эволюция C++» , что ему понравилась концепция мультиметодов, и он рассматривал возможность реализации ее на C++, но утверждает, что не смог найти эффективный образец реализации (сравнимый с виртуальными функциями) и решить некоторые возможные проблемы неоднозначности типов. Затем он заявляет, что, хотя эту функцию все равно было бы неплохо иметь, ее можно приблизительно реализовать с использованием двойной диспетчеризации или таблицы поиска на основе типов, как показано в примере C/C++ выше, поэтому это функция с низким приоритетом для будущих версий языка. [22]

По состоянию на 2021 год Как и многие другие объектно-ориентированные языки программирования, D изначально поддерживает только одиночную диспетчеризацию. Однако в D можно эмулировать открытые мультиметоды как библиотечные функции. openmethods Библиотека [23] это пример.

// DeclarationMatrix plus(virtual!Matrix, virtual!Matrix);// The override for two DenseMatrix objects@methodMatrix _plus(DenseMatrix a, DenseMatrix b){  const int nr = a.rows;  const int nc = a.cols;  assert(a.nr == b.nr);  assert(a.nc == b.nc);  auto result = new DenseMatrix;  result.nr = nr;  result.nc = nc;  result.elems.length = a.elems.length;  result.elems[] = a.elems[] + b.elems[];  return result;}// The override for two DiagonalMatrix objects@methodMatrix _plus(DiagonalMatrix a, DiagonalMatrix b){  assert(a.rows == b.rows);  double[] sum;  sum.length = a.elems.length;  sum[] = a.elems[] + b.elems[];  return new DiagonalMatrix(sum);}

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

UML-класс Java, одиночная диспетчеризация.svg

interface Collideable {    void collideWith(final Collideable other);    /* These methods would need different names in a language without method overloading. */    void collideWith(final Asteroid asteroid);    void collideWith(final Spaceship spaceship);}class Asteroid implements Collideable {    public void collideWith(final Collideable other) {        // Call collideWith on the other object.        other.collideWith(this);   }    public void collideWith(final Asteroid asteroid) {        // Handle Asteroid-Asteroid collision.    }    public void collideWith(final Spaceship spaceship) {        // Handle Asteroid-Spaceship collision.    }}class Spaceship implements Collideable {    public void collideWith(final Collideable other) {        // Call collideWith on the other object.        other.collideWith(this);    }    public void collideWith(final Asteroid asteroid) {        // Handle Spaceship-Asteroid collision.    }    public void collideWith(final Spaceship spaceship) {        // Handle Spaceship-Spaceship collision.    }}

Время выполнения instanceof Также можно использовать проверки на одном или обоих уровнях.

Поддержка языков программирования

[ редактировать ]

Первичная парадигма

[ редактировать ]

Поддержка общих мультиметодов

[ редактировать ]

Через расширения

[ редактировать ]

См. также

[ редактировать ]
  1. ^ Ранка, Санджай; Банерджи, Арунава; Бисвас, Канад Кишор; Дуа, Сумит; Мишра, Прабхат; Муна, Раджат (26 июля 2010 г.). Современные вычисления: Вторая международная конференция, IC3 2010, Нойда, Индия, 9–11 августа 2010 г. Труды . Спрингер. ISBN  9783642148248 .
  2. ^ Перейти обратно: а б с д и ж г час я дж к Мушевичи, Раду; Потанин, Алексей; Темперо, Юэн; Ноубл, Джеймс (2008). «Множественная рассылка на практике». Материалы 23-й конференции ACM SIGPLAN по языкам и приложениям объектно-ориентированных систем программирования . УПСЛА '08. Нэшвилл, Теннесси, США: ACM. стр. 563–582. дои : 10.1145/1449764.1449808 . ISBN  9781605582153 . S2CID   7605233 .
  3. ^ Перейти обратно: а б с д и Безансон, Джефф; Эдельман, Алан; Карпински, Стефан; Шах, Вирал Б. (7 февраля 2017 г.). «Юлия: Свежий подход к численным вычислениям». Обзор СИАМ . 59 (1): 65–98. arXiv : 1411.1607 . дои : 10.1137/141000671 . S2CID   13026838 .
  4. ^ Кастанья, Джузеппе; Гелли, Джорджио и Лонго, Джузеппе (1995). «Исчисление перегруженных функций с подтипированием» . Информация и вычисления . 117 (1): 115–135. дои : 10.1006/inco.1995.1033 .
  5. ^ Кастанья, Джузеппе (1996). Объектно-ориентированное программирование: единая основа . Прогресс в теоретической информатике. Биркхойзер. п. 384. ИСБН  978-0-8176-3905-1 .
  6. ^ Кастанья, Джузеппе (1995). «Ковариантность и контравариантность: конфликт без причины». Транзакции ACM в языках и системах программирования . 17 (3): 431–447. CiteSeerX   10.1.1.115.5992 . дои : 10.1145/203095.203096 . S2CID   15402223 .
  7. ^ Брюс, Ким; Карделли, Лука; Кастанья, Джузеппе; Ливенс, Гэри Т.; Пирс, Бенджамин (1995). «О бинарных методах» . Теория и практика объектных систем . 1 (3): 221–242. дои : 10.1002/j.1096-9942.1995.tb00019.x . Проверено 19 апреля 2013 г.
  8. ^ «Использование динамического типа (Руководство по программированию на C#)» . Проверено 14 мая 2020 г.
  9. ^ «Основные понятия» . Проверено 14 мая 2020 г.
  10. ^ «Dynamic .NET — понимание динамического ключевого слова в C# 4» . Проверено 14 мая 2020 г.
  11. ^ Groovy — Мультиметоды
  12. ^ @arrows/multimethod Множественная диспетчеризация в JavaScript/TypeScript с настраиваемым разрешением диспетчеризации, автор Maciej Cąderek.
  13. ^ Коуди, Арик, мультиметод: диспетчеризация нескольких аргументов. , получено 28 января 2021 г.
  14. ^ multimethods.py. Архивировано 9 марта 2005 г. в Wayback Machine . Множественная диспетчеризация в Python с настраиваемым разрешением диспетчеризации, Дэвид Мерц и др.
  15. ^ «Мультиметоды за пять минут на Python» .
  16. ^ «ПИК-Правила 0.5a1.dev» . Индекс пакетов Python . Проверено 21 марта 2014 г.
  17. ^ «Пи-протоколы» . Комплект корпоративных приложений Python . Проверено 26 апреля 2019 г.
  18. ^ «Рег» . Прочтите документы . Проверено 26 апреля 2019 г.
  19. ^ «Объектная система C: структура, которая выводит C на уровень других языков программирования высокого уровня и выше: CObjectSystem/COS» . Гитхаб . 2019-02-19.
  20. ^ «Отчет о языковой поддержке Multi-Methods и Open-Methods для C++» (PDF) . 11 марта 2007 г. Множественная диспетчеризация — выбор вызываемой функции на основе динамического типа двух или более аргументов — является решением нескольких классических проблем объектно-ориентированного программирования.
  21. ^ yomm2 , Быстрые ортогональные открытые мультиметоды для C++, Жан-Луи Леруа.
  22. ^ Страуструп, Бьярн (1994). «Раздел 13.8». Проектирование и эволюция C++ . Индианаполис, Индиана, США: Эддисон Уэсли. Бибкод : 1994декабрь..книга.....S . ISBN  978-0-201-54330-8 .
  23. ^ openmethods , Открытые мультиметоды для D Жана-Луи Леруа.
  24. ^ «Методы» . Руководство Юлии . Джулиаланг. Архивировано из оригинала 17 июля 2016 года . Проверено 11 мая 2014 г.
  25. ^ «Мультиметоды в C# 4.0 с динамическим интерфейсом » . Проверено 20 августа 2009 г.
  26. ^ «Язык Сесила» . Проверено 13 апреля 2008 г.
  27. ^ «Мультиметоды в Clojure» . Проверено 4 сентября 2008 г.
  28. ^ Стил, Гай Л. (1990). «28» . Общий LISP: язык . Бедфорд, Массачусетс, США: Digital Press. ISBN  978-1-55558-041-4 .
  29. ^ «Предыстория и цели» . Проверено 13 апреля 2008 г.
  30. ^ «Спецификация языка Fortress, версия 1.0» (PDF) . Архивировано из оригинала (PDF) 20 января 2013 г. Проверено 23 апреля 2010 г.
  31. ^ «Мультиметоды в Groovy» . Проверено 13 апреля 2008 г.
  32. ^ «Методы – ЛассоГид 9.2» . Проверено 11 ноября 2014 г.
  33. ^ «Шаблон посетителя и мультиметоды» . Проверено 13 апреля 2008 г.
  34. ^ «Руководство Нима: Мультиметоды» . Проверено 3 мая 2022 г.
  35. ^ «Часто задаваемые вопросы по Perl 6» . Проверено 13 апреля 2008 г.
  36. ^ «Как работают методы S4» (PDF) . Проверено 13 апреля 2008 г.
  37. ^ «Множественная отправка в Seed7» . Проверено 23 апреля 2011 г.
  38. ^ «Руководство по системе TADS 3» . Проверено 19 марта 2012 г.
  39. ^ «Множественная рассылка VB.Net» . Проверено 31 марта 2020 г.
  40. ^ «Новые возможности C# 4.0 и VB.Net 10.0» . 4 ноября 2010 г. Проверено 31 марта 2020 г.
  41. ^ «Заметки для знатоков языков программирования» . Проверено 21 августа 2016 г.
  42. ^ «Множественная отправка» .
[ редактировать ]
Arc.Ask3.Ru: конец переведенного документа.
Arc.Ask3.Ru
Номер скриншота №: 5d377b25814551a9476b0e0ccc01313f__1714032780
URL1:https://arc.ask3.ru/arc/aa/5d/3f/5d377b25814551a9476b0e0ccc01313f.html
Заголовок, (Title) документа по адресу, URL1:
Multiple dispatch - Wikipedia
Данный printscreen веб страницы (снимок веб страницы, скриншот веб страницы), визуально-программная копия документа расположенного по адресу URL1 и сохраненная в файл, имеет: квалифицированную, усовершенствованную (подтверждены: метки времени, валидность сертификата), открепленную ЭЦП (приложена к данному файлу), что может быть использовано для подтверждения содержания и факта существования документа в этот момент времени. Права на данный скриншот принадлежат администрации Ask3.ru, использование в качестве доказательства только с письменного разрешения правообладателя скриншота. Администрация Ask3.ru не несет ответственности за информацию размещенную на данном скриншоте. Права на прочие зарегистрированные элементы любого права, изображенные на снимках принадлежат их владельцам. Качество перевода предоставляется как есть. Любые претензии, иски не могут быть предъявлены. Если вы не согласны с любым пунктом перечисленным выше, вы не можете использовать данный сайт и информация размещенную на нем (сайте/странице), немедленно покиньте данный сайт. В случае нарушения любого пункта перечисленного выше, штраф 55! (Пятьдесят пять факториал, Денежную единицу (имеющую самостоятельную стоимость) можете выбрать самостоятельно, выплаичвается товарами в течение 7 дней с момента нарушения.)