El envío múltiple o métodos múltiples es una característica de algunos lenguajes de programación en el que una función o método pueden ser dinámicamente sean enviados basan en el tiempo de ejecución de tipo (dinámica) o, en el caso más general, algún otro atributo de más de uno de sus argumentos . [1] Esta es una generalización del polimorfismo de envío único.donde una llamada a una función o método se distribuye dinámicamente en función del tipo derivado del objeto en el que se ha llamado al método. El envío múltiple enruta el envío dinámico a la función o método de implementación utilizando las características combinadas de uno o más argumentos.
Entendiendo el despacho
Los desarrolladores de software de computadora generalmente organizan el código fuente en bloques con nombre llamados subrutinas , procedimientos, subprogramas, funciones o métodos. El código de la función se ejecuta llamándolo , ejecutando un fragmento de código que hace referencia a su nombre . Esto transfiere el control temporalmente a la función llamada; cuando la ejecución de la función se ha completado, el control normalmente se transfiere de nuevo a la instrucción en el llamador que sigue a la referencia.
Los nombres de las funciones generalmente se seleccionan para que sean descriptivos del propósito de la función. A veces es deseable dar el mismo nombre a varias funciones, a menudo porque realizan tareas conceptualmente similares, pero operan con diferentes tipos de datos de entrada. En tales casos, la referencia de nombre en el sitio de llamada de función no es suficiente para identificar el bloque de código que se ejecutará. En su lugar, el número y el tipo de argumentos de la llamada a la función también se utilizan para seleccionar entre varias implementaciones de funciones.
En lenguajes de programación orientados a objetos más convencionales, es decir, de despacho único , al invocar un método ( enviar un mensaje en Smalltalk , llamar a una función miembro en C ++ ), uno de sus argumentos se trata especialmente y se usa para determinar cuál de los (potencialmente se aplicarán muchas) clases de métodos con ese nombre. En muchos idiomas, el argumento especial se indica sintácticamente; por ejemplo, varios lenguajes de programación ponen el argumento especial antes de un punto al hacer una llamada al método:, de special.method(other, arguments, here)
modo que lion.sound()
produciría un rugido, mientras sparrow.sound()
que produciría un chirrido.
Por el contrario, en lenguajes con envío múltiple, el método seleccionado es simplemente aquel cuyos argumentos coinciden con el número y tipo de la llamada a la función. No hay ningún argumento especial que posea la función / método que se lleva a cabo en una llamada en particular.
El Common Lisp Object System (CLOS) es un ejemplo temprano y bien conocido de envío múltiple.
Tipos de datos
Cuando se trabaja con lenguajes que pueden discriminar tipos de datos en el momento de la compilación , es posible seleccionar entre las alternativas. El acto de crear tales funciones alternativas para la selección del tiempo de compilación generalmente se denomina sobrecarga de una función.
En los lenguajes de programación que difieren la identificación del tipo de datos hasta el tiempo de ejecución (es decir, vinculación tardía ), la selección entre funciones alternativas debe ocurrir entonces, basándose en los tipos de argumentos de función determinados dinámicamente. Las funciones cuyas implementaciones alternativas se seleccionan de esta manera se denominan más generalmente métodos múltiples .
Existe algún costo de tiempo de ejecución asociado con el envío dinámico de llamadas a funciones. En algunos lenguajes, [ cita requerida ] la distinción entre sobrecarga y métodos múltiples puede ser borrosa, con el compilador determinando si la selección de tiempo de compilación se puede aplicar a una llamada de función dada, o si se necesita un despacho de tiempo de ejecución más lento.
Utilizar en la práctica
Para estimar la frecuencia con la que se utiliza el envío múltiple en la práctica, Muschevici et al. [2] estudió programas que utilizan despacho dinámico. Analizaron nueve aplicaciones, en su mayoría compiladores, escritas en seis lenguajes diferentes: Common Lisp Object System , Dylan , Cecil , MultiJava, Diesel y Nice. Sus resultados muestran que entre el 13% y el 32% de las funciones genéricas utilizan el tipo dinámico de un argumento, mientras que entre el 2,7% y el 6,5% utilizan el tipo dinámico de múltiples argumentos. El 65-93% restante de las funciones genéricas tienen un método concreto (overrider) y, por lo tanto, no se considera que utilicen los tipos dinámicos de sus argumentos. Además, el estudio informa que del 2 al 20% de las funciones genéricas tenían dos y del 3 al 6% tenían tres implementaciones de funciones concretas. Los números disminuyen rápidamente para funciones con anulaciones más concretas.
El envío múltiple se usa mucho más en Julia , donde el envío múltiple fue un concepto de diseño central desde el origen del lenguaje: al recopilar las mismas estadísticas que Muschevici sobre el número promedio de métodos por función genérica, se encontró que la biblioteca estándar de Julia usa más del doble de sobrecarga que en los otros lenguajes analizados por Muschevici, y más de 10 veces en el caso de los operadores binarios . [3]
Los datos de estos artículos se resumen en la siguiente tabla, donde el índice de despacho DR
es el número promedio de métodos por función genérica; la razón de elección CR
es la media del cuadrado del número de métodos (para medir mejor la frecuencia de funciones con un gran número de métodos); [2] [3] y el grado de especialización DoS
es el número promedio de argumentos especializados en tipos por método (es decir, el número de argumentos que se envían):
Idioma | Promedio de # métodos (DR) | Razón de elección (CR) | Grado de especialización (DoS) |
---|---|---|---|
Cecil [2] | 2,33 | 63.30 | 1.06 |
Common Lisp ( CMU ) [2] | 2,03 | 6.34 | 1,17 |
Common Lisp ( McCLIM ) [2] | 2,32 | 15.43 | 1,17 |
Common Lisp ( Steel Bank ) [2] | 2,37 | 26,57 | 1,11 |
Diésel [2] | 2,07 | 31,65 | 0,71 |
Dylan (Gwydion) [2] | 1,74 | 18.27 | 2.14 |
Dylan (OpenDylan) [2] | 2.51 | 43,84 | 1,23 |
Julia [3] | 5,86 | 51,44 | 1,54 |
Julia (solo operadores) [3] | 28.13 | 78.06 | 2.01 |
MultiJava [2] | 1,50 | 8,92 | 1.02 |
Agradable [2] | 1,36 | 3,46 | 0,33 |
Teoría
La teoría de múltiples lenguajes de despacho fue desarrollada por primera vez por Castagna et al., Al definir un modelo para funciones sobrecargadas con enlace tardío . [4] [5] Produjo la primera formalización del problema de covarianza y contravarianza de los lenguajes orientados a objetos [6] y una solución al problema de los métodos binarios. [7]
Ejemplos de
La distinción entre envíos múltiples y únicos se puede aclarar con un ejemplo. Imagina un juego que tiene, entre sus objetos (visibles para el usuario), naves espaciales y asteroides. Cuando dos objetos chocan, es posible que el programa deba hacer cosas diferentes de acuerdo con lo que acaba de golpear.
Idiomas con envío múltiple incorporado
C#
C # introdujo soporte para métodos múltiples dinámicos en la versión 4 [8] (abril de 2010) usando la palabra clave 'dinámica'. El siguiente ejemplo muestra varios métodos, aunque se puede lograr un efecto similar con expresiones de cambio desde la versión 8 [9] (septiembre de 2019). Como muchos otros lenguajes de tipo estático, C # también admite la sobrecarga de métodos estáticos. [10] Microsoft espera que los desarrolladores elijan la escritura estática sobre la dinámica en la mayoría de los escenarios. [11] La palabra clave 'dinámica' admite la interoperabilidad con objetos COM y lenguajes .NET de tipo dinámico.
class Programa { static void Main () { Console . WriteLine ( Collider . Collide ( nuevo asteroide ( 101 ), nueva nave espacial ( 300 ))); Consola . WriteLine ( Collider . Collide ( nuevo asteroide ( 10 ), nueva nave espacial ( 10 ))); Consola . WriteLine ( Collider . Collide ( nueva nave espacial ( 101 ), nueva nave espacial ( 10 ))); } }static class Collider { cadena estática pública Collide ( SpaceObject x , SpaceObject y ) => (( x . Size > 100 ) && ( y . Size > 100 )) ? "¡Gran boom!" : CollideWith ( x como dinámico , y como dinámico ); privada estática cadena CollideWith ( asteroide x , asteroides y ) => "un / a" ; privada estática cadena CollideWith ( asteroide x , la nave espacial y ) => "a / s" ; privada estática cadena CollideWith ( Nave espacial x , asteroides y ) => "s / a" ; privada estática cadena CollideWith ( Nave espacial x , la nave espacial y ) => "s / s" ; } abstracta clase SpaceObject { público SpaceObject ( int tamaño ) => Tamaño = tamaño ; public int Size { get ; } }clase Asteroide : SpaceObject { Asteroide público ( tamaño int ) : base ( tamaño ) { } } clase Nave espacial : SpaceObject { nave espacial pública ( tamaño int ) : base ( tamaño ) { } }
Producción:
gran boomcomos / s
Groovy
Groovy es un lenguaje JVM interusable / compatible con Java de uso general que, a diferencia de Java, utiliza enlace tardío / envío múltiple. [12]
/ * Implementación maravillosa del ejemplo de C # anterior El enlace tardío funciona igual cuando se usan métodos no estáticos o se compilan clases / métodos estáticamente (anotación @CompileStatic) * / class Program { static void main ( String [] args ) { println Collider . collide ( nuevo asteroide ( 101 ), nueva nave espacial ( 300 )) println Collider . collide ( nuevo asteroide ( 10 ), nueva nave espacial ( 10 )) println Collider . collide ( nueva nave espacial ( 101 ), nueva nave espacial ( 10 )) } }class Collider { cadena estática collide ( SpaceObject x , SpaceObject y ) { ( x . tamaño > 100 && y . tamaño > 100 ) ? "big-boom" : collideWith ( x , y ) // Despacho dinámico para collideWith método } Cadena estática privada collideWith ( Asteroide x , Asteroide y ) { "a / a" } Cadena estática privada collideWith ( Asteroide x , Nave espacial y ) { "a / s" } Cadena estática privada collideWith ( Nave espacial x , Asteroide y ) { "s / a" } privada estática cadena collideWith ( Nave espacial x , la nave espacial y ) { "s / s" } } class SpaceObject { tamaño int SpaceObject ( tamaño int ) { this . tamaño = tamaño } } @InheritConstructors clase Asteroid extiende SpaceObject {} @InheritConstructors clase Spaceship extiende SpaceObject {}
Lisp común
En un lenguaje con distribución múltiple, como Common Lisp , podría parecerse más a esto (se muestra un ejemplo de Common Lisp):
( defmethod collide-with (( x asteroide ) ( y asteroide )) ;; lidiar con asteroide chocando contra asteroide ) ( defmethod collide-with (( x asteroide ) ( y nave espacial )) ;; lidiar con asteroide golpeando nave espacial ) ( defmethod collide- con (( x nave espacial ) ( y asteroide )) ;; lidiar con nave espacial golpeando asteroide ) ( defmethod collide-with (( x nave espacial ) ( y nave espacial )) ;; lidiar con nave espacial golpeando nave espacial )
y de manera similar para los otros métodos. No se utilizan pruebas explícitas ni "conversión dinámica".
En presencia de envío múltiple, la idea tradicional de que los métodos están definidos en clases y contenidos en objetos se vuelve menos atractiva: cada método de colisión con anterior se adjunta a dos clases diferentes, no a una. Por lo tanto, la sintaxis especial para la invocación de métodos generalmente desaparece, de modo que la invocación de métodos se ve exactamente como la invocación de funciones ordinarias y los métodos no se agrupan en clases sino en funciones genéricas .
Julia
Julia ha incorporado el envío múltiple y es fundamental para el diseño del lenguaje. [3] La versión de Julia del ejemplo anterior podría verse así:
collide_with ( x :: Asteroid , y :: Asteroid ) = ... # lidiar con el asteroide chocando con el asteroide collide_with ( x :: Asteroid , y :: Spaceship ) = ... # lidiar con el asteroide chocando con la nave espacial collide_with ( x :: Spaceship , y :: Asteroide ) = ... # tratar con nave espacial chocando con el asteroide collide_with ( x :: Nave espacial , y :: Nave espacial ) = ... # tratar con nave espacial golpeando nave espacial
Shell de próxima generación
Next Generation Shell tiene un envío múltiple integrado y un envío de predicado, y son fundamentales para el diseño del lenguaje. [13]
Los métodos con el mismo nombre forman un método de envío múltiple, por lo que no se requiere una declaración especial.
Cuando se llama a un método de envío múltiple, el método candidato se busca de abajo hacia arriba. Siempre que los tipos de argumentos coinciden con los tipos especificados para los parámetros, se invoca el método. Eso contrasta con muchos otros idiomas donde gana la coincidencia específica más tipográfica. Dentro de un método invocado, una guardia que falla (donde la condición de la guardia se evalúa como falsa) hace que continúe la búsqueda del método a invocar.
{ tipo SpaceObject tipo Asteroide ( SpaceObject ) tipo Nave espacial ( SpaceObject ) }F init ( o : SpaceObject , tamaño : Int ) o . tamaño = tamañoF colisionar ( x : asteroide , y : asteroide ) "a / a" F colisionar ( x : asteroide , y : nave espacial ) "a / s" F colisionar ( x : nave espacial , y : asteroide ) "s / a" F colisionar ( x : nave espacial , y : nave espacial ) "s / s"F colisionar ( x : SpaceObject , y : SpaceObject ) { guarda x . tamaño > 100 guardia y . tamaño > 100 "big-boom" }echo ( colisionar ( Asteroide ( 101 ), Nave espacial ( 300 ))) echo ( colisionar ( Asteroide ( 10 ), Nave espacial ( 10 )))
Producción:
gran boomcomo
Raku
Raku , como Perl, usa ideas probadas de otros lenguajes, y los sistemas de tipos han demostrado ofrecer ventajas convincentes en el análisis de código del lado del compilador y una poderosa semántica del lado del usuario a través del envío múltiple.
Tiene tanto multimétodos como multisubs. Dado que la mayoría de los operadores son subrutinas, también tiene varios operadores asignados.
Junto con las restricciones de tipo habituales, también tiene restricciones donde permiten realizar subrutinas muy especializadas.
subconjunto Masa de Real donde 0 ^ .. ^ Inf ; role Stellar-Object { tiene Mass $ .mass es obligatorio ; nombre del método () devuelve Str {...}; }class Asteroid does Stellar-Object { nombre del método () { 'un asteroide' }}clase Spaceship does Stellar-Object { tiene Str $ .name = 'alguna nave espacial sin nombre' ;}mi Str @destroyed = < destruido destruido destrozado >;mi Str @damaged = « dañado 'chocó con' 'fue dañado por' »;# Agregamos múltiples candidatos a los operadores de comparación numéricos porque los estamos comparando numéricamente, # pero no tiene sentido que los objetos formen un tipo numérico. # (Si lo coaccionaran, no necesariamente necesitaríamos agregar estos operadores). # También podríamos haber definido operadores completamente nuevos de la misma manera. multi sub infijo: «<=>» ( Objeto estelar: D $ a , Objeto estelar: D $ b ) { $ a . masa <=> $ b . mass } multi sub infijo: «<» ( Objeto estelar: D $ a , Objeto estelar: D $ b ) { $ a . masa < $ b . mass } multi sub infijo: «>» ( Objeto estelar: D $ a , Objeto estelar: D $ b ) { $ a . masa > $ b . mass } multi sub infijo: «==» ( Objeto estelar: D $ a , Objeto estelar: D $ b ) { $ a . masa == $ b . masa }# Defina un nuevo despachador múltiple y agregue algunas restricciones de tipo a los parámetros. # Si no lo definimos, habríamos obtenido uno genérico que no tenía restricciones. proto sub collide ( Objeto estelar: D $, Objeto estelar: D $) {*}# No es necesario repetir los tipos aquí, ya que son los mismos que los del prototipo. # La restricción 'donde' técnicamente solo se aplica a $ b, no a toda la firma. # Tenga en cuenta que la restricción 'where' usa el operador candidato `<` que agregamos anteriormente. multi sub collide ( $ a , $ b donde $ a < $ b ) { digamos "$ a.name () fue @ destrozado.pick () por $ b.name ()" ;}multi sub collide ( $ a , $ b donde $ a > $ b ) { # redispatch al candidato anterior con los argumentos intercambiados igual con $ b , $ a ;}# Esto tiene que ser después de los dos primeros porque los otros # tienen restricciones 'where', que se comprueban en el # orden en que se escribieron los subs. (Este siempre coincidiría.) Multi sub collide ( $ a , $ b ) { # aleatorizar el orden my ( $ n1 , $ n2 ) = ( $ a . Nombre , $ b . Nombre ). escoger (*); diga "$ n1 @ dañado.pick () $ n2" ;}# Los siguientes dos candidatos pueden estar en cualquier lugar después del proto, # porque tienen tipos más especializados que los tres anteriores.# Si los barcos tienen una masa desigual, se llama a uno de los dos primeros candidatos. multi sub collide ( Nave espacial $ a , Nave espacial $ b donde $ a == $ b ) { my ( $ n1 , $ n2 ) = ( $ a . nombre , $ b . nombre ). escoger (*); decir "$ n1 chocó con $ n2, y ambos barcos fueron" , ( @destroyed . pick , 'dejado dañado' ). recoger ;}# Puede descomprimir los atributos en variables dentro de la firma. # Incluso podría tener una restricción sobre ellos `(: mass ($ a) donde 10)`. multi sub collide ( Asteroide $ (: masa ( $ a )), Asteroide $ (: masa ( $ b ))) { digamos "dos asteroides chocaron y combinaron en un asteroide más grande de masa {$ a + $ b}" ;}my Spaceship $ Enterprise . = new (: mass ( 1 ) ,: name ( 'The Enterprise' ));colisionar asteroide . nuevo (: masivo ( .1 )), $ Enterprise ;colisionar $ Enterprise , Spaceship . nuevo (: masa ( .1 ));colisionar $ Enterprise , Asteroide . nuevo (: masa ( 1 ));colisionar $ Enterprise , Spaceship . nuevo (: masa ( 1 ));colisionar asteroide . nuevo (: masa ( 10 )), Asteroide . nuevo (: masa ( 5 ));
Ampliación de lenguajes con bibliotecas de envío múltiple
JavaScript
En los lenguajes que no admiten el envío múltiple en la definición del lenguaje o el nivel sintáctico, a menudo es posible agregar un envío múltiple utilizando una extensión de biblioteca . JavaScript y TypeScript no admiten métodos múltiples a nivel de sintaxis, pero es posible agregar varios envíos a través de una biblioteca. Por ejemplo, el paquete multimétodo [14] proporciona una implementación de funciones genéricas de envío múltiple.
Versión escrita dinámicamente en JavaScript:
importar { multi , method } de '@ flechas / multimethod'clase Asteroide {} clase Nave espacial {}const collideWith = multi ( método ([ Asteroide , Asteroide ], ( x , y ) => { // tratar con asteroide golpeando asteroide }), método ([ Asteroide , nave espacial ], ( x , y ) => { // tratar con asteroide golpeando una nave espacial }), método ([ Nave espacial , Asteroide ], ( x , y ) => { // tratar con nave espacial golpeando un asteroide }), método ([ Nave espacial , Nave espacial ], ( x , y ) => { / / lidiar con nave espacial golpeando nave espacial }), )
Versión de tipo estático en TypeScript:
importar { multi , method , Multi } de '@ flechas / multimethod'clase Asteroide {} clase Nave espacial {}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 ( método ([ Asteroide , Asteroide ], ( x , y ) => { // tratar con un asteroide que golpea un asteroide }), método ([ Asteroide , Nave espacial ], ( x , y ) => { / / tratar con un asteroide que golpea una nave espacial }), método ([ Nave espacial , asteroide ], ( x , y ) => { // tratar con una nave espacial que golpea un asteroide }), método ([ Nave espacial , Nave espacial ], ( x , y ) => { // lidiar con nave espacial golpeando nave espacial }), )
Pitón
Se pueden agregar varios envíos a Python mediante una extensión de biblioteca . Por ejemplo, usando el módulo multimethod.py [15] y también con el módulo multimethods.py [16] que proporciona métodos múltiples de estilo CLOS para Python sin cambiar la sintaxis subyacente o las palabras clave del lenguaje.
desde multimethods import Dispatch from game_objects import Asteroid , Spaceship from game_behaviors import as_func , ss_func , sa_func collide = Dispatch () collide . add_rule (( Asteroide , Nave espacial ), as_func ) chocan . add_rule (( Nave espacial , Nave espacial ), ss_func ) colisionan . add_rule (( Nave espacial , Asteroide ), sa_func ) def aa_func ( a , b ): "" "Comportamiento cuando un asteroide choca contra un asteroide." "" # ... define un nuevo comportamiento ... colisiona . add_rule (( Asteroide , Asteroide ), aa_func )
# ... luego ... colisionar ( cosa1 , cosa2 )
Funcionalmente, esto es muy similar al ejemplo de CLOS, pero la sintaxis es Python convencional.
Usando decoradores de Python 2.4 , Guido van Rossum produjo una implementación de muestra de métodos múltiples [17] con una sintaxis simplificada:
@multimethod ( asteroide , asteroide ) def collide ( a , b ): "" "Comportamiento cuando un asteroide choca contra un asteroide." "" # ... define un nuevo comportamiento ... @multimethod ( asteroide , nave espacial ) def collide ( a , b ): "" "Comportamiento cuando un asteroide golpea una nave espacial." "" # ... definir un nuevo comportamiento ... # ... definir otras reglas de métodos múltiples ...
y luego pasa a definir el decorador de métodos múltiples.
El paquete PEAK-Rules proporciona envío múltiple con una sintaxis similar a la del ejemplo anterior. [18] Posteriormente fue reemplazado por PyProtocols. [19]
La biblioteca Reg también admite el envío de predicados múltiples. [20]
Emulando el envío múltiple
C
C no tiene envío dinámico, por lo que debe implementarse manualmente de alguna forma. A menudo, se usa una enumeración para identificar el subtipo de un objeto. El despacho dinámico se puede realizar buscando este valor en una tabla de rama de puntero de función . Aquí hay un ejemplo simple en C:
typedef void ( * CollisionCase ) ( vacío );void collision_AA ( void ) { / * manejar la colisión Asteroide-Asteroide * / }; void collision_AS ( void ) { / * manejar la colisión Asteroide-Nave espacial * / }; void collision_SA ( void ) { / * manejar la colisión nave espacial-asteroide * / }; void collision_SS ( void ) { / * manejar la colisión nave espacial-nave espacial * / };typedef enum { THING_ASTEROID = 0 , THING_SPACESHIP , THING_COUNT / * no es un tipo de cosa en sí, sino que se usa para encontrar el número de cosas * / } Thing ;CollisionCase collisionCases [ THING_COUNT ] [ THING_COUNT ] = { { & collision_AA , & collision_AS }, { & collision_SA , & collision_SS } };void collide ( cosa a , cosa b ) { ( * collisionCases [ a ] [ b ]) (); }int main ( void ) { colisionar ( THING_SPACESHIP , THING_ASTEROID ); }
Con la biblioteca C Object System, [21] C admite el envío dinámico similar a CLOS. Es completamente extensible y no necesita ningún manejo manual de los métodos. Los mensajes dinámicos (métodos) son enviados por el despachador de COS, que es más rápido que Objective-C. Aquí hay un ejemplo en COS:
#include #include #include // clasesdefclass ( Asteroid ) // miembros de datos endclassdefclass ( Spaceship ) // miembros de datos endclass// genéricosdefgeneric ( _Bool , collide_with , _1 , _2 );// métodos múltiplesdefmethod ( _Bool , collide_with , Asteroid , Asteroid ) // lidiar con el asteroide que golpea el método final del asteroidedefmethod ( _Bool , collide_with , Asteroid , Spaceship ) // lidiar con el asteroide que golpea la nave espacial endmethoddefmethod ( _Bool , collide_with , Spaceship , Asteroid ) // lidiar con la nave espacial que golpea el asteroide endmethoddefmethod ( _Bool , collide_with , Spaceship , Spaceship ) // lidiar con la nave espacial que golpea la nave espacial endmethod// ejemplo de usoint main ( void ) { OBJ a = gnew ( Asteroide ); OBJ s = gnew ( nave espacial ); printf ( " =% d ,>\ n " , colisionar_con ( a , a )); printf ( " =% d ,>\ n " , colisionar_con ( a , s )); printf ( " =% d ,>\ n " , colisionar_con ( s , a )); printf ( " =% d ,>\ n " , colisionar_con ( s , s )); grelease ( a ); grelease ( s ); }
C ++
A partir de 2021[actualizar], C ++ soporta de forma nativa un solo envío, aunque Bjarne Stroustrup (y colaboradores) propuso agregar múltiples métodos (envío múltiple ) en 2007. [22] Los métodos para trabajar alrededor de este límite son análogos: use el patrón de visitante , el reparto dinámico o una biblioteca:
// Ejemplo de uso de la comparación de tipos de tiempo de ejecución a través de dynamic_cast struct Thing { collideWith vacío virtual ( Thing & other ) = 0 ; }; struct Asteroid : Thing { void collideWith ( Thing & other ) { // dynamic_cast a un tipo de puntero devuelve NULL si el lanzamiento falla // (dynamic_cast a un tipo de referencia arrojaría una excepción en caso de falla) if ( auto asteroid = dynamic_cast < Asteroid * > ( & other )) { // manejar la colisión Asteroide-Asteroide } else if ( auto spaceship = dynamic_cast < Spaceship *> ( & other )) { // manejar la colisión Asteroide-Nave espacial } else { // manejo predeterminado de la colisión aquí } } }; 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 *> ( & otro )) { // manejar la colisión nave espacial-nave espacial } else { // manejo predeterminado de la colisión aquí } } };
o tabla de búsqueda de puntero a método:
#include #include #include class Thing { protegido : Thing ( std :: uint32_t cid ) : tid ( cid ) {} const std :: uint32_t tid ; // tipo 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 , controlador CollisionHandler ) { collisionCases . insert ( CollisionHandlerMap :: value_type ( clave ( id1 , id2 ), controlador )); } std estático :: uint64_t clave ( 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 . encontrar ( clave ( tid , otro . tid )); if ( manejador ! = CollisionCases . end ()) { ( este -> * manejador -> segundo ) ( otro ); // llamada de puntero a método } else { // manejo de colisiones predeterminado } } };class Asteroid : public Thing { void asteroid_collision ( Thing & other ) { / * maneja la colisión de asteroide-asteroide * / } void spaceship_collision ( Thing & other ) { / * maneja la colisión de asteroide-nave espacial * / } public : Asteroide () : Cosa ( cid ) {} static void initCases (); static const std :: uint32_t cid ; };class Spaceship : public Thing { void asteroid_collision ( Thing & other ) { / * maneja la colisión entre nave espacial y asteroide * / } void spaceship_collision ( Thing & other ) { / * maneja la colisión entre nave espacial y nave espacial * / } public : Spaceship () : Thing ( cid ) {} static void initCases (); static const std :: uint32_t cid ; // id de clase };Thing :: CollisionHandlerMap Thing :: collisionCases ; const std :: uint32_t Asteroide :: cid = typeid ( Asteroide ). hash_code (); const std :: uint32_t Nave espacial :: cid = typeid ( Nave espacial ). 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 () { Asteroide :: initCases (); Nave espacial :: initCases (); Asteroide a1 , a2 ; Nave espacial s1 , s2 ; a1 . collideWith ( a2 ); a1 . collideWith ( s1 ); s1 . collideWith ( s2 ); s1 . collideWith ( a1 ); }
La biblioteca yomm2 [23] proporciona una implementación ortogonal rápida de métodos múltiples abiertos.
La sintaxis para declarar métodos abiertos está inspirada en una propuesta para una implementación nativa de C ++. La biblioteca requiere que el usuario registre todas las clases utilizadas como argumentos virtuales (y sus subclases), pero no requiere ninguna modificación al código existente. Los métodos se implementan como funciones ordinarias de C ++ en línea; se pueden sobrecargar y se pueden pasar por puntero. No hay límite en el número de argumentos virtuales y se pueden mezclar arbitrariamente con argumentos no virtuales.
La biblioteca utiliza una combinación de técnicas (tablas de despacho comprimidas, hash de entero perfecto) para implementar llamadas a métodos en tiempo constante, al tiempo que mitiga el uso de la memoria. Despachar una llamada a un método abierto con un solo argumento virtual toma solo un 15-30% más de tiempo que llamar a una función miembro virtual ordinaria, cuando se usa un compilador optimizador moderno.
El ejemplo de Asteroides se puede implementar de la siguiente manera:
#include usando yorel :: yomm2 :: virtual_ ;class Thing { public : virtual ~ Thing () {} // ... };clase Asteroide : cosa pública { // ... }; clase Nave espacial : cosa pública { // ... }; register_class ( cosa ); register_class ( Nave espacial , cosa ); register_class ( Asteroide , Cosa );declare_method ( void , collideWith , ( virtual_ < Thing &> , virtual_ < Thing &> ));define_method ( void , collideWith , ( Thing & left , Thing & right )) { // manejo de colisiones predeterminado }define_method ( void , collideWith , ( Asteroide e izquierda , Asteroide y derecha )) { // manejar la colisión Asteroide-Asteroide }define_method ( void , collideWith , ( Asteroide e izquierda , Nave espacial y derecha )) { // manejar la colisión Asteroide-Nave espacial }define_method ( void , collideWith , ( Spaceship & left , Asteroid & right )) { // manejar la colisión entre nave espacial y asteroide }define_method ( void , collideWith , ( Spaceship & left , Spaceship & right )) { // manejar la colisión Spaceship-Spaceship }int main () { yorel :: yomm2 :: update_methods (); Asteroide a1 , a2 ; Nave espacial s1 , s2 ; collideWith ( a1 , a2 ); collideWith ( a1 , s1 ); collideWith ( s1 , s2 ); collideWith ( s1 , a1 ); }
Stroustrup menciona en The Design and Evolution of C ++ que le gustó el concepto de métodos múltiples y consideró implementarlo en C ++, pero afirma no haber podido encontrar una implementación de muestra eficiente (comparable a las funciones virtuales) y resolver algunos posibles problemas de ambigüedad de tipo. Luego afirma que, aunque sería bueno tener la característica, que se puede implementar aproximadamente usando el envío doble o una tabla de búsqueda basada en tipos como se describe en el ejemplo de C / C ++ anterior, por lo que es una característica de baja prioridad para futuras revisiones del lenguaje. [24]
D
A partir de 2021[actualizar], al igual que muchos otros lenguajes de programación orientados a objetos, D admite de forma nativa un único despacho. Sin embargo, es posible emular métodos múltiples abiertos como una función de biblioteca en D. La biblioteca openmethods [25] es un ejemplo.
// Declaración de la matriz más ( Virtual ! Matrix , Virtual ! Matriz );// La anulación para dos objetos DenseMatrix @method Matrix _plus ( DenseMatrix a , DenseMatrix b ) { const int nr = a . filas ; const int nc = a . cols ; afirmar ( a . nr == b . nr ); afirmar ( a . nc == b . nc ); resultado automático = new DenseMatrix ; resultado . nr = nr ; resultado . nc = nc ; resultado . elems . longitud = a . elems . longitud ; resultado . elems [] = a . elems [] + b . elems []; devolver resultado ; } // La anulación para dos objetos DiagonalMatrix @method Matrix _plus ( DiagonalMatrix a , DiagonalMatrix b ) { assert ( a . Rows == b . Rows ); doble [] suma ; suma . longitud = a . elems . longitud ; suma [] = a . elems [] + b . elems []; return new DiagonalMatrix ( suma ); }
Java
En un lenguaje con un solo envío, como Java , el envío múltiple se puede emular con varios niveles de envío único:
interfaz Collideable { void collideWith ( final Collideable otro ); / * Estos métodos necesitarían nombres diferentes en un idioma sin sobrecargarlos. * / void collideWith ( asteroide asteroide final ); void collideWith ( nave espacial final de la nave espacial ); } class Asteroid implementa Collideable { public void collideWith ( final Collideable other ) { // Llama a collideWith en el otro objeto. otros . collideWith ( esto ); } public void collideWith ( asteroide asteroide final ) { // Manejar la colisión asteroide-asteroide. } public void collideWith ( nave espacial final de la nave espacial ) { // Manejar la colisión asteroide-nave espacial. } }class Spaceship implementa Collideable { public void collideWith ( final Collideable other ) { // Llama a collideWith en el otro objeto. otros . collideWith ( esto ); } public void collideWith ( asteroide asteroide final ) { // Manejar la colisión nave espacial-asteroide. } public void collideWith ( nave espacial final nave espacial ) { // Manejar la colisión nave espacial-nave espacial. } }
instanceof
También se pueden utilizar comprobaciones de tiempo de ejecución en uno o ambos niveles.
Soporte en lenguajes de programación
Paradigma primario
- Julia [26]
Apoyar métodos múltiples generales
- C # 4.0 [27]
- Cecil [28]
- Clojure [29]
- Common Lisp (a través del sistema de objetos Common Lisp ) [30]
- Dylan [31]
- Elixir [32]
- Fortaleza [33]
- Maravilloso [34]
- Lazo [35] [36]
- Nim , hasta v0.19.x (los métodos múltiples quedaron obsoletos en v0.20) [37]
- Raku [38]
- R [39]
- Semilla7 [40]
- TADS [41]
- VB.Net [42] mediante enlace tardío, también mediante .Net DLR [43]
- Wolfram Language [44] mediante coincidencia de patrones simbólicos
- Xtend [45]
A través de extensiones
- Cualquier idioma .NET (a través de la biblioteca MultiMethods.NET )
- C (a través de la biblioteca C Object System )
- C # (a través de la biblioteca multimethod-sharp )
- C ++ (a través de la biblioteca yomm2 y multimethods )
- D (a través de la biblioteca openmethods )
- Factor (a través del vocabulario estándar de métodos múltiples )
- Java (usando la extensión MultiJava )
- JavaScript (a través de paquete @ flechas / multimetodo )
- Perl (a través del módulo Class :: Multimethods )
- Python (a través de PEAK-Rules , RuleDispatch , gnosis.magic.multimethods , PyMultimethods o multipledispatch )
- Raqueta (a través de multimethod-lib )
- Ruby (a través de la biblioteca The Multiple Dispatch Library and Multimethod Package y Vlx-Multimethods Package )
- Esquema (a través de, por ejemplo, TinyCLOS )
- TypeScript (a través de paquete @ flechas / multimetodo )
Ver también
- Despacho de predicados
Referencias
- ^ Ranka, Sanjay; Banerjee, Arunava; Biswas, Kanad Kishore; Dua, Sumeet; Mishra, Prabhat; Moona, Rajat (26 de julio de 2010). Computación contemporánea: Segunda Conferencia Internacional, IC3 2010, Noida, India, 9 al 11 de agosto de 2010. Actas . Saltador. ISBN 9783642148248.
- ^ a b c d e f g h yo j k Muschevici, Radu; Potanin, Alex; Tempero, Ewan; Noble, James (2008). Envío múltiple en la práctica . Actas de la 23ª Conferencia ACM SIGPLAN sobre lenguajes y aplicaciones de sistemas de programación orientados a objetos . OOPSLA '08. Nashville, TN, Estados Unidos: ACM. págs. 563–582. doi : 10.1145 / 1449764.1449808 . ISBN 9781605582153. S2CID 7605233 .
- ^ a b c d e Bezanson, Jeff; Edelman, Alan; Karpinski, Stefan; Shah, Viral B. (7 de febrero de 2017). "Julia: un nuevo enfoque de la computación numérica". Revisión SIAM . 59 (1): 65–98. arXiv : 1411.1607 . doi : 10.1137 / 141000671 . S2CID 13026838 .
- ^ Castagna, Giuseppe; Ghelli, Giorgio y Longo, Giuseppe (1995). "Un cálculo para funciones sobrecargadas con subtipos" . Información y Computación . 117 (1): 115-135. doi : 10.1006 / inco.1995.1033 . Consultado el 19 de abril de 2013 .
- ^ Castagna, Giuseppe (1996). Programación orientada a objetos: una base unificada . Progreso en Informática Teórica. Birkhäuser. pag. 384. ISBN 978-0-8176-3905-1.
- ^ Castagna, Giuseppe (1995). "Covarianza y contravarianza: conflicto sin causa". Transacciones ACM sobre lenguajes y sistemas de programación . 17 (3): 431–447. CiteSeerX 10.1.1.115.5992 . doi : 10.1145 / 203095.203096 . S2CID 15402223 .
- ^ Bruce, Kim; Cardelli, Luca; Castagna, Giuseppe; Leavens, Gary T .; Pierce, Benjamin (1995). "Sobre métodos binarios" . Teoría y práctica de sistemas de objetos . 1 (3): 221–242. doi : 10.1002 / j.1096-9942.1995.tb00019.x . Consultado el 19 de abril de 2013 .
- ^ "Uso de tipo dinámico (Guía de programación de C #)" . Consultado el 14 de mayo de 2020 .
- ^ "cambiar expresión (referencia de C #)" . Consultado el 14 de mayo de 2020 .
- ^ "Conceptos básicos" . Consultado el 14 de mayo de 2020 .
- ^ "Dynamic .NET: comprensión de la palabra clave dinámica en C # 4" . Consultado el 14 de mayo de 2020 .
- ^ Groovy - Múltiples métodos
- ^ "Manual de usuario de NGSLANG (1) NGS" . ngs-lang.org . Consultado el 1 de octubre de 2019 .
- ^ @ flechas / multimethod Envío múltiple en JavaScript / TypeScript con resolución de envío configurable por Maciej Cąderek.
- ^ Coady, Aric, multimethod: envío de múltiples argumentos. , consultado el 28 de enero de 2021
- ^ multimethods.py Archivado 2005-03-09 en Wayback Machine , Envío múltiple en Python con resolución de envío configurable por David Mertz, et al.
- ^ "Métodos múltiples de cinco minutos en Python" .
- ^ "Reglas PEAK 0.5a1.dev" . Índice de paquetes de Python . Consultado el 21 de marzo de 2014 .
- ^ "PyProtocols" . Kit de aplicaciones empresariales Python . Consultado el 26 de abril de 2019 .
- ^ "Reg" . Lea los documentos . Consultado el 26 de abril de 2019 .
- ^ "C Object System: un marco que lleva C al nivel de otros lenguajes de programación de alto nivel y más allá: CObjectSystem / COS" . 2019-02-19.
- ^ "Informe sobre el soporte de idiomas para métodos múltiples y métodos abiertos para C ++" (PDF) . 2007-03-11.
El envío múltiple, la selección de una función que se invocará en función del tipo dinámico de dos o más argumentos, es una solución a varios problemas clásicos de la programación orientada a objetos.
- ^ yomm2 , Multi-métodos abiertos ortogonales rápidos para C ++ por Jean-Louis Leroy.
- ^ Stroustrup, Bjarne (1994). "Sección 13.8". El diseño y la evolución de C ++ . Indianápolis, IN, EE.UU .: Addison Wesley. Bibcode : 1994dec..book ..... S . ISBN 978-0-201-54330-8.
- ^ openmethods , Open Multi-Methods for D por Jean-Louis Leroy.
- ^ "Métodos" . El Manual de Julia . Julialang. Archivado desde el original el 17 de julio de 2016 . Consultado el 11 de mayo de 2014 .
- ^ "Múltiples métodos en C # 4.0 con 'Dinámico ' " . Consultado el 20 de agosto de 2009 .
- ^ "Lengua Cecil" . Consultado el 13 de abril de 2008 .
- ^ "Multimétodos en Clojure" . Consultado el 4 de septiembre de 2008 .
- ^ Steele, Guy L. (1990). "28" . LISP común: el lenguaje . Bedford, MA, Estados Unidos: Digital Press. ISBN 978-1-55558-041-4.
- ^ "Antecedentes y objetivos" . Consultado el 13 de abril de 2008 .
- ^ "Elixir Lang | Getting Started | Módulos y funciones" . Consultado el 10 de noviembre de 2017 .
- ^ "The Fortress Language Specification, versión 1.0" (PDF) . Archivado desde el original (PDF) el 20 de enero de 2013 . Consultado el 23 de abril de 2010 .
- ^ "Múltiples métodos en Groovy" . Consultado el 13 de abril de 2008 .
- ^ "Métodos - LassoGuide 9.2" . Consultado el 11 de noviembre de 2014 .
- ^ "Patrón de visitante versus métodos múltiples" . Consultado el 13 de abril de 2008 .
- ^ "Nim Manual: Multi-métodos" . Consultado el 11 de septiembre de 2020 .
- ^ "Preguntas frecuentes de Perl 6" . Consultado el 13 de abril de 2008 .
- ^ "Cómo funcionan los métodos S4" (PDF) . Consultado el 13 de abril de 2008 .
- ^ "Envío múltiple en Seed7" . Consultado el 23 de abril de 2011 .
- ^ "Manual del sistema TADS 3" . Consultado el 19 de marzo de 2012 .
- ^ "Envío múltiple de VB.Net" . Consultado el 31 de marzo de 2020 .
- ^ "Nuevas características en C # 4.0 y VB.Net 10.0" . Consultado el 31 de marzo de 2020 .
- ^ "Notas para expertos en lenguajes de programación" . Consultado el 21 de agosto de 2016 .
- ^ "Envío múltiple" .
enlaces externos
- Stroustrup, Bjarne; Solodkyy, Yuriy; Pirkelbauer, Peter (2007). Abra Multi-Methods para C ++ (PDF) . VI Congreso Internacional ACM sobre Programación Generativa e Ingeniería de Componentes.
- "Despacho múltiple dinámico" . docs.racket-lang.org . Consultado el 12 de marzo de 2018 .