Envio múltiplo - Multiple dispatch

Despacho múltiplo ou multimétodos é um recurso de algumas linguagens de programação em que uma função ou método pode ser despachado dinamicamente com base no tipo de tempo de execução (dinâmico) ou, no caso mais geral, algum outro atributo de mais de um de seus argumentos . Esta é uma generalização do polimorfismo de despacho único, em que uma função ou chamada de método é despachada dinamicamente com base no tipo derivado do objeto no qual o método foi chamado. O envio múltiplo roteia o envio dinâmico para a função ou método de implementação usando as características combinadas de um ou mais argumentos.

Entendendo despacho

Os desenvolvedores de software de computador geralmente organizam o código-fonte em blocos nomeados, chamados de sub-rotinas , procedimentos, subprogramas, funções ou métodos. O código na função é executado chamando -a - executando um trecho de código que faz referência a seu nome . Isso transfere o controle temporariamente para a função chamada; quando a execução da função é concluída, o controle é normalmente transferido de volta para a instrução no chamador que segue a referência.

Os nomes das funções são geralmente selecionados de forma a serem descritivos da finalidade da função. Às vezes, é desejável dar o mesmo nome a várias funções, geralmente porque elas executam tarefas conceitualmente semelhantes, mas operam em diferentes tipos de dados de entrada. Nesses casos, a referência do nome no local da chamada de função não é suficiente para identificar o bloco de código a ser executado. Em vez disso, o número e o tipo dos argumentos para a chamada de função também são usados ​​para selecionar entre várias implementações de função.

Em linguagens de programação orientadas a objetos de envio único , mais convencionais, ao invocar um método ( enviar uma mensagem em Smalltalk , chamar uma função de membro em C ++ ), um de seus argumentos é tratado especialmente e usado para determinar qual dos (potencialmente muitas) classes de métodos com esse nome devem ser aplicadas. Em muitos idiomas, o argumento especial é indicado sintaticamente; por exemplo, várias linguagens de programação colocam o argumento especial antes de um ponto ao fazer uma chamada de método:, de special.method(other, arguments, here)modo que lion.sound()isso produziria um rugido, ao passo sparrow.sound()que produziria um chilro.

Em contraste, em linguagens com envio múltiplo, o método selecionado é simplesmente aquele cujos argumentos correspondem ao número e tipo da chamada de função. Não há nenhum argumento especial que possui a função / método executado em uma chamada particular.

O Common Lisp Object System (CLOS) é um exemplo antigo e bem conhecido de despacho múltiplo.

Tipos de dados

Ao trabalhar com linguagens que podem discriminar tipos de dados em tempo de compilação , a seleção entre as alternativas pode ocorrer então. O ato de criar tais funções alternativas para seleção de tempo de compilação é geralmente referido como sobrecarregar uma função.

Em linguagens de programação que adiam a identificação do tipo de dados até o tempo de execução (ou seja, ligação tardia ), a seleção entre funções alternativas deve ocorrer então, com base nos tipos de argumentos de função determinados dinamicamente. As funções cujas implementações alternativas são selecionadas desta maneira são referidas mais geralmente como multimétodos .

Há algum custo de tempo de execução associado ao despacho dinâmico de chamadas de função. Em algumas linguagens, a distinção entre sobrecarga e métodos multimétodos pode ser borrada, com o compilador determinando se a seleção de tempo de compilação pode ser aplicada a uma determinada chamada de função ou se o despacho de tempo de execução mais lento é necessário.

Use na prática

Para estimar a frequência com que o envio múltiplo é usado na prática, Muschevici et al. programas estudados que usam despacho dinâmico. Eles analisaram nove aplicativos, a maioria compiladores, escritos em seis linguagens diferentes: Common Lisp Object System , Dylan , Cecil , MultiJava, Diesel e Nice. Seus resultados mostram que 13–32% das funções genéricas usam o tipo dinâmico de um argumento, enquanto 2,7–6,5% delas usam o tipo dinâmico de vários argumentos. Os 65-93% restantes das funções genéricas têm um método concreto (overrider) e, portanto, não são considerados para usar os tipos dinâmicos de seus argumentos. Além disso, o estudo relata que 2–20% das funções genéricas tinham duas e 3–6% tinham três implementações de função concretas. Os números diminuem rapidamente para funções com overriders mais concretos.

O envio múltiplo é usado muito mais fortemente em Julia , onde o despacho múltiplo era um conceito central de design desde a origem da linguagem: coletando as mesmas estatísticas de Muschevici sobre o número médio de métodos por função genérica, descobriu-se que a biblioteca padrão Julia usa mais do que o dobro da quantidade de sobrecarga do que nas outras línguas analisadas por Muschevici, e mais de 10 vezes no caso de operadores binários .

Os dados desses artigos estão resumidos na tabela a seguir, onde a razão de despacho DRé o número médio de métodos por função genérica; a razão de escolha CRé a média do quadrado do número de métodos (para melhor medir a frequência das funções com um grande número de métodos); e o grau de especialização DoSé o número médio de argumentos especializados em tipo por método (ou seja, o número de argumentos que são despachados):

Língua Número médio de métodos (DR) Razão de escolha (CR) Grau de especialização (DoS)
Cecil 2,33 63,30 1.06
Lisp Comum ( CMU ) 2.03 6,34 1,17
Lisp Comum ( McCLIM ) 2,32 15,43 1,17
Lisp Comum ( Banco de Aço ) 2,37 26,57 1,11
Diesel 2.07 31,65 0,71
Dylan (Gwydion) 1,74 18,27 2,14
Dylan (OpenDylan) 2,51 43,84 1,23
Julia 5,86 51,44 1,54
Julia (apenas operadores) 28,13 78,06 2.01
MultiJava 1,50 8,92 1.02
Legal 1,36 3,46 0,33

Teoria

A teoria de múltiplas linguagens de despacho foi desenvolvida pela primeira vez por Castagna et al., Definindo um modelo para funções sobrecarregadas com ligação tardia . Ele rendeu a primeira formalização do problema de covariância e contravariância de linguagens orientadas a objetos e uma solução para o problema de métodos binários.

Exemplos

A distinção entre despacho único e múltiplo pode ficar mais clara por meio de um exemplo. Imagine um jogo que tenha, entre seus objetos (visíveis pelo usuário), naves espaciais e asteróides. Quando dois objetos colidem, o programa pode precisar fazer coisas diferentes de acordo com o que acabou de atingir o quê.

Idiomas com envio múltiplo integrado

C #

C # introduziu suporte para multimétodos dinâmicos na versão 4 (abril de 2010) usando a palavra-chave 'dynamic'. O exemplo a seguir demonstra multimétodos, embora um efeito semelhante possa ser alcançado com expressões de switch desde a versão 8 (setembro de 2019). Como muitas outras linguagens tipadas estaticamente, C # também oferece suporte à sobrecarga de método estático. A Microsoft espera que os desenvolvedores escolham a digitação estática em vez da digitação dinâmica na maioria dos cenários. A palavra-chave 'dynamic' oferece suporte à interoperabilidade com objetos COM e linguagens .NET digitadas dinamicamente.

class Program
{
    static void Main()
    {
        Console.WriteLine(Collider.Collide(new Asteroid(101),  new Spaceship(300)));
        Console.WriteLine(Collider.Collide(new Asteroid(10),   new Spaceship(10)));
        Console.WriteLine(Collider.Collide(new Spaceship(101), new Spaceship(10)));
    }
}

static class Collider
{
    public static string Collide(SpaceObject x, SpaceObject y) =>
        ((x.Size > 100) && (y.Size > 100)) ?
            "Big boom!" : CollideWith(x as dynamic, y as dynamic);
    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";
}

abstract class SpaceObject
{
    public SpaceObject(int size) => Size = size;

    public int Size { get; }
}

class Asteroid : SpaceObject
{
    public Asteroid(int size) : base(size) { }
}

class Spaceship : SpaceObject
{
    public Spaceship(int size) : base(size) { }
}

Resultado:

big-boom
a/s
s/s

Groovy

Groovy é uma linguagem JVM compatível / interusável com Java de propósito geral que, ao contrário do Java, usa ligação tardia / envio múltiplo.

/*
    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 {}

Lisp Comum

Em uma linguagem com envio múltiplo, como Common Lisp , pode se parecer mais com isto (exemplo de Common Lisp mostrado):

(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
  )

e da mesma forma para os outros métodos. Teste explícito e "conversão dinâmica" não são usados.

Na presença de despacho múltiplo, a ideia tradicional de métodos como sendo definidos em classes e contidos em objetos torna-se menos atraente - cada método de colisão com acima é anexado a duas classes diferentes, não a uma. Conseqüentemente, a sintaxe especial para invocação de método geralmente desaparece, de modo que a invocação de método se parece exatamente com a invocação de função comum e os métodos são agrupados não em classes, mas em funções genéricas .

Julia

Julia possui despacho múltiplo integrado e é fundamental para o design da linguagem. A versão Julia do exemplo acima pode ser semelhante a:

collide_with(x::Asteroid, y::Asteroid) = ... # deal with asteroid hitting asteroid
collide_with(x::Asteroid, y::Spaceship) = ... # deal with asteroid hitting spaceship
collide_with(x::Spaceship, y::Asteroid) = ... # deal with spaceship hitting asteroid
collide_with(x::Spaceship, y::Spaceship) = ... # deal with spaceship hitting spaceship

Shell de próxima geração

O Next Generation Shell possui despacho múltiplo integrado e despacho predicado, e eles são essenciais para o design da linguagem.

Métodos com o mesmo nome formam um método de despacho múltiplo, portanto, nenhuma declaração especial é necessária.

Quando um método de despacho múltiplo é chamado, o método candidato é pesquisado de baixo para cima. Sempre que os tipos dos argumentos correspondem aos tipos especificados para os parâmetros, o método é invocado. Isso contrasta com muitas outras linguagens em que a correspondência mais específica do tipo vence. Dentro de um método invocado, um guarda com falha (onde a condição do guarda é avaliada como falsa) faz com que a busca do método a ser invocado continue.

{
	type SpaceObject
	type Asteroid(SpaceObject)
	type Spaceship(SpaceObject)
}

F init(o:SpaceObject, size:Int) o.size = size

F collide(x:Asteroid, y:Asteroid) "a/a"
F collide(x:Asteroid, y:Spaceship) "a/s"
F collide(x:Spaceship, y:Asteroid) "s/a"
F collide(x:Spaceship, y:Spaceship) "s/s"

F collide(x:SpaceObject, y:SpaceObject) {
	guard x.size > 100
	guard y.size > 100
	"big-boom"
}

echo(collide(Asteroid(101), Spaceship(300)))
echo(collide(Asteroid(10), Spaceship(10)))

Resultado:

big-boom
a/s

Raku

O Raku , como o Perl, usa ideias comprovadas de outras linguagens, e os sistemas de tipos têm mostrado que oferecem vantagens atraentes na análise de código do lado do compilador e semântica poderosa do lado do usuário por meio de despacho múltiplo.

Ele tem vários métodos e multisubs. Como a maioria dos operadores são sub-rotinas, ele também possui vários operadores despachados.

Junto com as restrições de tipo usuais, também possui restrições onde que permitem fazer sub-rotinas muito especializadas.

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

Extensão de linguagens com bibliotecas de despacho múltiplo

JavaScript

Em linguagens que não suportam despacho múltiplo na definição de linguagem ou nível sintático, geralmente é possível adicionar despacho múltiplo usando uma extensão de biblioteca . JavaScript e TypeScript não suportam multimétodos no nível de sintaxe, mas é possível adicionar envio múltiplo por meio de uma biblioteca. Por exemplo, o pacote multimétodo fornece uma implementação de múltiplas funções genéricas de despacho.

Versão digitada dinamicamente em 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
  }),
)

Versão digitada estaticamente em 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
  }),
)

Pitão

O envio múltiplo pode ser adicionado ao Python usando uma extensão de biblioteca . Por exemplo, usando o módulo multimethod.py e também com o módulo multimethods.py que fornece multimétodos no estilo CLOS para Python sem alterar a sintaxe subjacente ou palavras-chave da linguagem.

from multimethods import Dispatch
from game_objects import Asteroid, Spaceship
from game_behaviors import as_func, ss_func, sa_func
collide = 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)

Funcionalmente, é muito semelhante ao exemplo CLOS, mas a sintaxe é Python convencional.

Usando decoradores Python 2.4 , Guido van Rossum produziu uma implementação de exemplo de multimétodos com uma sintaxe simplificada:

@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 ...

e então define o decorador multimétodo.

O pacote PEAK-Rules fornece despacho múltiplo com uma sintaxe semelhante ao exemplo acima. Posteriormente, foi substituído por PyProtocols.

A biblioteca Reg também suporta envio múltiplo e predicado.

Emulando envio múltiplo

C

C não tem despacho dinâmico, portanto, deve ser implementado manualmente de alguma forma. Freqüentemente, um enum é usado para identificar o subtipo de um objeto. O despacho dinâmico pode ser feito procurando este valor em uma tabela de ramificação de ponteiro de função . Aqui está um exemplo simples em 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);
}

Com a biblioteca C Object System, C suporta despacho dinâmico semelhante ao CLOS. É totalmente extensível e não requer nenhum tratamento manual dos métodos. Mensagens dinâmicas (métodos) são despachadas pelo despachante de COS, que é mais rápido que Objective-C. Aqui está um exemplo em COS:

#include <stdio.h>
#include <cos/Object.h>
#include <cos/gen/object.h>

// classes

defclass (Asteroid)
// data members
endclass

defclass (Spaceship)
// data members
endclass

// generics

defgeneric (_Bool, collide_with, _1, _2);

// multimethods

defmethod (_Bool, collide_with, Asteroid, Asteroid)
 // deal with asteroid hitting asteroid
endmethod

defmethod (_Bool, collide_with, Asteroid, Spaceship)
 // deal with asteroid hitting spaceship
endmethod

defmethod (_Bool, collide_with, Spaceship, Asteroid)
 // deal with spaceship hitting asteroid
endmethod

defmethod (_Bool, collide_with, Spaceship, Spaceship)
 // deal with spaceship hitting spaceship
endmethod

// example of use

int 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);
}

C ++

A partir de 2021, C ++ suporta nativamente apenas um único despacho, embora a adição de métodos múltiplos (despacho múltiplo) tenha sido proposto por Bjarne Stroustrup (e colaboradores) em 2007. Os métodos de trabalho em torno deste limite são análogos: use o padrão do visitante , elenco dinâmico ou uma biblioteca:

 // 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
         }
     }
 };

ou tabela de pesquisa de ponteiro para método:

#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);
}

A biblioteca yomm2 fornece uma implementação ortogonal rápida de métodos multimétodos abertos.

A sintaxe para declarar métodos abertos é inspirada em uma proposta para uma implementação nativa de C ++. A biblioteca requer que o usuário registre todas as classes usadas como argumentos virtuais (e suas subclasses), mas não requer nenhuma modificação no código existente. Os métodos são implementados como funções C ++ embutidas comuns; eles podem ser sobrecarregados e podem ser passados ​​por ponteiro. Não há limite para o número de argumentos virtuais e eles podem ser arbitrariamente misturados com argumentos não virtuais.

A biblioteca usa uma combinação de técnicas (tabelas de despacho compactadas, hash de inteiro perfeito) para implementar chamadas de método em tempo constante, enquanto mitiga o uso de memória. Despachar uma chamada para um método aberto com um único argumento virtual leva apenas 15-30% mais tempo do que chamar uma função de membro virtual comum, quando um compilador de otimização moderno é usado.

O exemplo de Asteroids pode ser implementado da seguinte forma:

#include <yorel/yomm2/cute.hpp>

using yorel::yomm2::virtual_;

class Thing {
  public:
    virtual ~Thing() {}
    // ...
};

class Asteroid : public Thing {
    // ...
};

class Spaceship : public Thing {
    // ...
};

register_class(Thing);
register_class(Spaceship, Thing);
register_class(Asteroid, Thing);

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();

    Asteroid  a1, a2;
    Spaceship s1, s2;

    collideWith(a1, a2);
    collideWith(a1, s1);

    collideWith(s1, s2);
    collideWith(s1, a1);
}

Stroustrup menciona em The Design and Evolution of C ++ que gostou do conceito de multimétodos e considerou implementá-lo em C ++, mas afirma não ter conseguido encontrar uma implementação de amostra eficiente (comparável a funções virtuais) e resolver alguns possíveis problemas de ambigüidade de tipo. Ele então afirma que, embora o recurso ainda seja bom de ter, ele pode ser aproximadamente implementado usando despacho duplo ou uma tabela de pesquisa baseada em tipo, conforme descrito no exemplo C / C ++ acima, portanto, é um recurso de baixa prioridade para futuras revisões de linguagem.

D

A partir de 2021, assim como muitas outras linguagens de programação orientadas a objetos, o D suporta nativamente apenas um único despacho. No entanto, é possível emular métodos abertos como uma função de biblioteca em D. A biblioteca de métodos abertos é um exemplo.

// Declaration
Matrix plus(virtual!Matrix, virtual!Matrix);

// The override for two DenseMatrix objects
@method
Matrix _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
@method
Matrix _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

Em uma linguagem com apenas um único despacho, como Java , vários despachos podem ser emulados com vários níveis de um único despacho:

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.
    }
}

As instanceofverificações de tempo de execução em um ou ambos os níveis também podem ser usadas.

Suporte em linguagens de programação

Paradigma primário

Suporte a métodos gerais de multimídia

Via extensões

Veja também

Referências

links externos

  • Stroustrup, Bjarne; Solodkyy, Yuriy; Pirkelbauer, Peter (2007). Abra Multi-métodos para C ++ (PDF) . ACM 6ª Conferência Internacional sobre Programação Gerativa e Engenharia de Componentes.
  • "Envio múltiplo dinâmico" . docs.racket-lang.org . Página visitada em 12/03/2018 .