stringtranslate.com

Despacho múltiple

El envío múltiple o multimétodos es una característica de algunos lenguajes de programación en los que una función o método se puede distribuir dinámicamente en función del tipo de tiempo de ejecución (dinámico) 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 envía dinámicamente en función del tipo derivado del objeto en el que se ha llamado el 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 suelen organizar el código fuente en bloques con nombres, denominados subrutinas , procedimientos, subprogramas, funciones o métodos. El código de la función se ejecuta llamándola , ejecutando un fragmento de código que hace referencia a su nombre . Esto transfiere el control temporalmente a la función llamada; cuando se completa la ejecución de la función, el control generalmente se transfiere de nuevo a la instrucción del llamador que sigue a la referencia.

Los nombres de las funciones generalmente se seleccionan de manera que describan el 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 del nombre en el sitio de llamada a la función no es suficiente para identificar el bloque de código que se ejecutará. En cambio, 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 envío ú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 de manera especial y se usa para determinar cuál de los (potencialmente) muchas) clases de métodos con ese nombre se van a aplicar. 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 realizar una llamada a un método: special.method(other, arguments, here), de modo que lion.sound()produciría un rugido, mientras que sparrow.sound()produciría un chirrido.

Por el contrario, en lenguajes con despacho múltiple, el método seleccionado es simplemente aquel cuyos argumentos coinciden con el número y tipo de llamada a la función. No existe ningún argumento especial que sea propietario de la función/método llevado 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. Otro ejemplo notable del uso del despacho múltiple es el lenguaje de programación Julia .

El envío múltiple debe distinguirse de la sobrecarga de funciones , en la que la información de escritura estática, como el tipo declarado o inferido de un término (o el tipo base en un lenguaje con subtipos) se usa para determinar cuál de varias posibilidades se usará en un sitio de llamada determinado. y esa determinación se realiza en el momento de la compilación o del enlace (o en algún otro momento antes de que comience la ejecución del programa) y posteriormente es invariante para una implementación o ejecución determinada del programa. Muchos lenguajes como C++ ofrecen una sobrecarga de funciones sólida, pero no ofrecen envío múltiple dinámico (C++ solo permite el envío único dinámico mediante el uso de funciones virtuales).

Tipos de datos

Cuando se trabaja con lenguajes que pueden discriminar tipos de datos en el momento de la compilación , se puede seleccionar entre las alternativas. El acto de crear funciones alternativas para la selección en 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, enlace tardío ), la selección entre funciones alternativas debe ocurrir en ese momento, en función de los tipos de argumentos de función determinados dinámicamente. Las funciones cuyas implementaciones alternativas se seleccionan de esta manera se denominan generalmente multimétodos .

Existe cierto costo de tiempo de ejecución asociado con el envío dinámico de llamadas a funciones. En algunos idiomas, [ cita necesaria ] la distinción entre sobrecarga y métodos múltiples puede ser borrosa, y el compilador determina si la selección del tiempo de compilación se puede aplicar a una llamada de función determinada o si se necesita un tiempo de ejecución más lento.

Asuntos

Hay varios problemas conocidos con el envío dinámico, tanto únicos como múltiples. Si bien muchos de estos problemas se resuelven con el envío único, que ha sido una característica estándar en los lenguajes de programación orientados a objetos durante décadas, estos problemas se vuelven más complicados en el caso del envío múltiple.

Expresividad y modularidad.

En los lenguajes de programación más populares, el código fuente se entrega y se implementa en gránulos de funcionalidad que aquí llamaremos paquetes ; La terminología real para este concepto varía según el idioma. Cada paquete puede contener múltiples definiciones de tipo, valor y función, los paquetes a menudo se compilan por separado en lenguajes con un paso de compilación y puede existir una relación de dependencia no cíclica. Un programa completo es un conjunto de paquetes, con un paquete principal que puede depender de varios otros paquetes, y todo el programa consiste en el cierre transitivo de la relación de dependencia.

El llamado problema de expresión se relaciona con la capacidad del código en un paquete dependiente para extender comportamientos (funciones o tipos de datos) definidos en un paquete base desde dentro de un paquete incluido, sin modificar la fuente del paquete base. Los lenguajes OO tradicionales de despacho único hacen que sea trivial agregar nuevos tipos de datos pero no nuevas funciones; Los lenguajes funcionales tradicionales tienden a tener el efecto opuesto y el envío múltiple, si se implementa correctamente, permite ambos. Es deseable que una implementación de despacho múltiple tenga las siguientes propiedades:

Ambigüedad

Generalmente es deseable que para cualquier invocación dada de un método múltiple, haya como máximo un "mejor" candidato entre los casos de implementación del método múltiple, y/o que si no lo hay, esto se resuelva de una manera predecible y manera determinista, incluido el fracaso. El comportamiento no determinista es indeseable. Suponiendo un conjunto de tipos con una relación de subtipos no circular, se puede definir que una implementación de un método múltiple es "mejor" (más específica) si todos los argumentos distribuidos dinámicamente en el primero son subtipos de todos los argumentos distribuidos dinámicamente especificados. en el segundo, y al menos uno es un subtipo estricto. Con despacho único y en ausencia de herencia múltiple , esta condición se cumple trivialmente, pero con despacho múltiple, es posible que dos o más candidatos satisfagan una lista de argumentos real dada, pero ninguno es más específico que el otro (un argumento dinámico siendo el subtipo en un caso, otro siendo el subtipo en el otro caso). Esto puede suceder particularmente si dos paquetes diferentes, que no dependen del otro, extienden algún método múltiple con implementaciones relacionadas con los tipos de cada paquete, y luego un tercer paquete que incluye ambos (posiblemente indirectamente) invoca el método múltiple usando argumentos de ambos. paquetes.

Las posibles resoluciones incluyen:

Eficiencia

Implementación eficiente de envío único, incluso en lenguajes de programación que se compilan por separado en código objeto y se vinculan con un vinculador de bajo nivel (que no reconoce el idioma), incluso dinámicamente en el momento de carga/inicio del programa o incluso bajo la dirección de la aplicación. código, son bien conocidos. El método " vtable " desarrollado en C++ y otros lenguajes OO tempranos (donde cada clase tiene una matriz de punteros de función correspondientes a las funciones virtuales de esa clase) es casi tan rápido como una llamada a un método estático, requiriendo una sobrecarga O(1) y sólo una llamada adicional. Búsqueda de memoria incluso en el caso no optimizado. Sin embargo, el método vtable utiliza el nombre de la función y no el tipo de argumento como clave de búsqueda, y no se escala al caso de envío múltiple. (También depende del paradigma orientado a objetos de que los métodos sean características de clases, no entidades independientes independientes de cualquier tipo de datos en particular).

La implementación eficiente del envío múltiple sigue siendo un problema de investigación en curso.

Uso en la práctica

Para estimar con qué frecuencia se utiliza el envío múltiple en la práctica, Muschevici et al. [2] estudiaron 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% de ellas utilizan el tipo dinámico de múltiples argumentos. El 65-93% restante de las funciones genéricas tienen un método concreto (anulación) y, por lo tanto, no se considera que utilicen los tipos dinámicos de sus argumentos. Además, el estudio informa que entre el 2% y el 20% de las funciones genéricas tenían dos y entre el 3% y el 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 era 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 descubrió 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 DRes el número promedio de métodos por función genérica; el índice de elección CRes 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 DoSes el número promedio de argumentos especializados en tipo por método (es decir, el número de argumentos que se envían):

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

La distinción entre envíos múltiples y únicos puede aclararse con un ejemplo. Imagine un juego que tenga, entre sus objetos (visibles para el usuario), naves espaciales y asteroides. Cuando dos objetos chocan, es posible que el programa deba hacer cosas diferentes según lo que acaba de golpear.

Idiomas con despacho múltiple incorporado

C#

C# introdujo soporte para multimétodos dinámicos en la versión 4 [8] (abril de 2010) utilizando la palabra clave 'dinámica'. El siguiente ejemplo demuestra múltiples métodos. Como muchos otros lenguajes de tipado estático, C# también admite la sobrecarga de métodos estáticos. [9] Microsoft espera que los desarrolladores elijan la escritura estática en lugar de la escritura dinámica en la mayoría de los escenarios. [10] La palabra clave 'dinámica' admite la interoperabilidad con objetos COM y lenguajes .NET de tipo dinámico.

El siguiente ejemplo utiliza características introducidas en C# 9 y C# 10.

usando ColliderLibrary estático ;  Consola . WriteLine ( Collide ( nuevo asteroide ( 101 ), nueva nave espacial ( 300 ))); Consola . WriteLine ( Collide ( nuevo asteroide ( 10 ), nueva nave espacial ( 10 ))); Consola . WriteLine ( Collide ( nueva nave espacial ( 101 ), nueva nave espacial ( 10 )));         cadena Chocar ( SpaceObject x , SpaceObject y ) => x . Talla > 100 && y . Tamaño > 100 ? "¡Gran boom!" : CollideWith ( x como dinámico , y como dinámico ); // Envío dinámico al método CollideWith                      class ColliderLibrary { cadena estática pública CollideWith ( Asteroide x , Asteroide y ) => "a/a" ; cadena estática pública CollideWith ( Asteroide x , Nave espacial y ) => "a/s" ; cadena estática pública CollideWith ( nave espacial x , asteroide y ) => "s/a" ; cadena estática pública CollideWith ( nave espacial x , nave espacial y ) => "s/s" ; }                                     registro abstracto SpaceObject ( int Tamaño ); registrar Asteroide ( int Tamaño ) : SpaceObject ( Tamaño ); registrar nave espacial ( int tamaño ) : objeto espacial ( tamaño );           

Producción:

¡Gran auge! a/s s/s

maravilloso

Groovy es un lenguaje JVM interutilizable/compatible con Java de propósito general que, a diferencia de Java, utiliza enlace tardío/despacho múltiple. [11]

/*  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  (@CompileStatic annotation) */ class Program { static void main ( String [] args ) { println Collider . colisionar ( nuevo asteroide ( 101 ), nueva nave espacial ( 300 )) println Collider . colisionar ( nuevo asteroide ( 10 ), nueva nave espacial ( 10 )) println Collider . colisionar ( nueva nave espacial ( 101 ), nueva nave espacial ( 10 )) } }                       clase Collider { cadena estática colisionar ( SpaceObject x , SpaceObject y ) { ( x . tamaño > 100 && y . tamaño > 100 ) ? "big-boom" : colisionar con ( x , y ) // Envío dinámico al método colisionar con }                        cadena estática privada choca con ( asteroide x , asteroide y ) { "a/a" } cadena estática privada choca con ( asteroide x , nave espacial y ) { "a/s" } cadena estática privada choca con ( nave espacial x , asteroide y ) { "s /a" } Cadena estática privada colisiona con ( Nave espacial x , Nave espacial y ) { "s/s" } }                                      clase SpaceObject { int tamaño SpaceObject ( int tamaño ) { this . tamaño = tamaño } }           @InheritConstructors clase Asteroide extiende SpaceObject {} @InheritConstructors clase Nave espacial extiende SpaceObject {}          

ceceo 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 choca con (( x asteroide ) ( y asteroide )) ;; lidiar con un asteroide que choca con un asteroide ) ( defmethod choca con (( x asteroide ) ( y nave espacial )) ;; lidiar con un asteroide que golpea una nave espacial ) ( defmethod choca con with (( x nave espacial ) ( y asteroide )) ;; lidiar con una nave espacial que choca contra un asteroide ) ( defmethod colisión-con (( x nave espacial ) ( y nave espacial )) ;; lidiar con una nave espacial que golpea una nave espacial )                            

y lo mismo para los otros métodos. No se utilizan pruebas explícitas ni "transmisión dinámica".

En presencia de despacho 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 anterior está asociado 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 tiene despacho múltiple incorporado y es fundamental para el diseño del lenguaje. [3] La versión de Julia del ejemplo anterior podría verse así:

tipo abstracto final SpaceObject   estructura  Asteroide <: tamaño del objeto espacial :: fin int estructura nave espacial <: tamaño del objeto espacial :: fin int         colisionar_con ( :: Asteroide , :: Nave espacial ) = "a/s" colisionar_con ( :: Nave espacial , :: Asteroide ) = "s/a" colisionar_con ( :: Nave espacial , :: Nave espacial ) = "s/s" colisionar_con ( :: Asteroide , :: Asteroide ) = "a/a"            colisionar ( x :: SpaceObject , y :: SpaceObject ) = ( x . tamaño > 100 && y . tamaño > 100 ) ? "¡Gran boom!" : colisionar_con ( x , y )              

Producción:

julia> colisionar ( Asteroide ( 101 ), Nave espacial ( 300 )) "¡Gran boom!"  julia> colisionar ( Asteroide ( 10 ), Nave espacial ( 10 )) "a/s"  julia> colisionar ( Nave espacial ( 101 ), Nave espacial ( 10 )) "s/s"  

rakú

Raku , al igual que Perl, utiliza 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 de envío múltiple.

Tiene tanto multimétodos como multisubs. Dado que la mayoría de los operadores son subrutinas, también tiene múltiples operadores enviados.

Junto con las restricciones de tipo habituales, también tiene restricciones donde que permiten realizar subrutinas muy especializadas.

subconjunto  Masa  de  Real  donde  0 ^..^ Inf ; rol  Objeto estelar { tiene  masa  $.  Se  requiere masa ; nombre del método () devuelve Str {...}; }clase  Asteroide  hace  Objeto Estelar { nombre del método () { 'un asteroide' }}clase  Nave espacial  hace  Objeto estelar { tiene  Str  $.name = 'alguna nave espacial sin nombre' ;}my  Str  @destroyed = < borrado  destruido destrozado  > ; my  Str  @damaged = «  dañado  'chocó con'  'fue dañado por'  »;# Agregamos múltiples candidatos a los operadores de comparación numérica porque los estamos comparando numéricamente, # pero no tiene sentido forzar los objetos a un tipo numérico. # (Si lo hicieran, 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 . masa } multi  sub  infijo: « < » ( Objeto estelar:D  $a , Objeto estelar:D  $b ) { $a . masa < $b . masa } multi  sub  infijo: « > » ( Objeto-estelar:D  $a , Objeto-estelar:D  $b ) { $a . masa > $b . masa } 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 tuviera restricciones. proto  sub  colisión ( 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 'dónde' técnicamente solo se aplica a $b, no a toda la firma. # Tenga en cuenta que la restricción 'dónde' utiliza el candidato a operador `<` que agregamos anteriormente. colisión múltiple ( $a , $b donde $a < $b ) { diga "$a.name() fue @destroyed.pick ( ) por $b.name()" ;    }multi  sub  colision ( $a , $b  donde  $a > $b ) { # redispacho 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 de 'dónde', que se verifican en el # orden en que se escribieron los subs. (Este siempre coincidiría. ) multi  sub  colisión ( $a , $b ) { # aleatorizar el orden  my ( $n1 , $n2 ) = ( $a . nombre , $b . nombre ). elegir (*); 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 masas desiguales, se llama a uno de los dos primeros candidatos. colisión múltiple ( Nave espacial $a , Nave espacial $b donde $a == $b ){ my ( $n1 , $n2 ) = ( $a . nombre , $b . nombre ). elegir (*); diga "$n1 chocó con $n2, y ambos barcos fueron" ,       ( @destroyed . pick , 'dejado dañado' ). elegir ;}# Puedes descomprimir los atributos en variables dentro de la firma. # Incluso podrías tener una restricción sobre ellos `(:mass($a) donde 10)`. colisión múltiple  ( Asteroide $ ( : masa ( $a )), Asteroide $ (: masa ( $b ))){ diga "dos asteroides chocaron y se combinaron en un asteroide más grande de masa { $a + $b }" ;  }mi  Nave Espacial  $Empresa .= new (: masa ( 1 ),: nombre ( 'La Empresa' )); colisionan con  un asteroide . nuevo (: masa ( .1 )), $Empresa ; colisionan  $Enterprise , Nave espacial . nuevo (: masa ( .1 )); colisionan  $Enterprise , Asteroide . nuevo (: masa ( 1 )); colisionan  $Enterprise , Nave espacial . nuevo (: masa ( 1 )); colisionan con  un asteroide . nuevo (: masa ( 10 )), Asteroide . nuevo (: masa ( 5 ));

Ampliación de idiomas con bibliotecas de envío múltiple

javascript

En los idiomas que no admiten el envío múltiple a nivel de definición de idioma o sintáctico, a menudo es posible agregar el envío múltiple mediante una extensión de biblioteca . JavaScript y TypeScript no admiten métodos múltiples a nivel de sintaxis, pero es posible agregar envíos múltiples a través de una biblioteca. Por ejemplo, el paquete multimétodo [12] proporciona una implementación de funciones genéricas de despacho múltiple.

Versión escrita dinámicamente en JavaScript:

importar { multi , método } desde '@arrows/multimethod'      clase Asteroide {} clase Nave espacial {}    const 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 golpeando una nave espacial }), método ([ Nave espacial , Asteroide ], ( x , y ) => { // lidiar con una nave espacial golpeando un asteroide }), método ([ Nave espacial , Nave espacial ], ( x , y ) => { / / lidiar con una nave espacial que choca contra una nave espacial }), )                                   

Versión escrita estáticamente en TypeScript:

importar { multi , método , Multi } desde '@arrows/multimethod'       clase Asteroide {} clase Nave espacial {}    escriba CollideWith = Multi & { ( x : Asteroide , y : Asteroide ) : vacío ( x : Asteroide , y : Nave espacial ) : vacío ( x : Nave espacial , y : Asteroide ) : vacío ( x : Nave espacial , y : Nave espacial ) : vacío }                         const colisionar con : CollideWith = multi ( método ([ Asteroide , Asteroide ], ( x , y ) => { // tratar con un asteroide que golpea un asteroide }), método ([ Asteroide , Nave espacial ], ( x , y ) => { / / lidiar con un asteroide que golpea una nave espacial }), método ([ Nave espacial , Asteroide ], ( x , y ) => { // lidiar con una nave espacial que golpea un asteroide }), método ([ Nave espacial , Nave espacial ], ( x , y ) => { // lidiar con una nave espacial que choca contra una nave espacial }), )                                    

Pitón

Se pueden agregar envíos múltiples a Python usando una extensión de biblioteca . Por ejemplo, usando el módulo multimethod.py [13] y también con el módulo multimethods.py [14] que proporciona multimétodos estilo CLOS para Python sin cambiar la sintaxis subyacente o las palabras clave del lenguaje.

desde  multimétodos  importar  Envío desde  game_objects  importar  asteroide ,  nave espacial desde  game_behaviors  importar  as_func ,  ss_func ,  sa_funccolisionar  =  Despacho () colisionar . add_rule (( asteroide ,  nave espacial ),  as_func ) colisionan . 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.""" # ...definir un nuevo comportamiento...  chocar . add_rule (( Asteroide ,  Asteroide ),  aa_func )
# ...más tarde... colisionar ( cosa1 ,  cosa2 )

Funcionalmente, esto es muy similar al ejemplo CLOS, pero la sintaxis es la convencional de Python.

Utilizando decoradores de Python 2.4 , Guido van Rossum produjo una implementación de muestra de multimétodos [15] con una sintaxis simplificada:

@multimethod ( Asteroide ,  Asteroide ) def  colisiona ( a ,  b ): """Comportamiento cuando un asteroide choca contra un asteroide.""" # ...define un nuevo comportamiento... @multimethod ( Asteroide , Nave espacial ) def colisiona ( a , b ): """Comportamiento cuando un asteroide choca contra una nave espacial.""" # ...definir nuevo comportamiento... # ... definir otras reglas multimétodo...       

y luego pasa a definir el decorador multimétodo.

El paquete PEAK-Rules proporciona envío múltiple con una sintaxis similar al ejemplo anterior. [16] Posteriormente fue reemplazado por PyProtocols. [17]

La biblioteca Reg también admite el envío múltiple y de predicados. [18]

Con la introducción de sugerencias de tipo , es posible el envío múltiple con una sintaxis aún más simple. Por ejemplo, usando plum-dispatch,

del  despacho de importación de ciruela  @dispatch def  colisionar ( a :  Asteroide ,  b :  Asteroide ): """Comportamiento cuando un asteroide choca contra otro.""" # ...definir nuevo comportamiento...   @dispatch def  colisionar ( a :  Asteroide ,  b :  Nave espacial ): """Comportamiento cuando un asteroide golpea una nave espacial.""" # ...definir nuevo comportamiento...   # ...definir reglas adicionales...

Emulando despacho múltiple

C

C no tiene despacho dinámico, por lo que debe implementarse manualmente de alguna forma. A menudo se utiliza una enumeración para identificar el subtipo de un objeto. El envío 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 vacío ( * CollisionCase ) ( vacío );  void Collision_AA ( void ) { /* manejar la colisión asteroide-asteroide */ }; void Collision_AS ( void ) { /* manejar la colisión entre asteroides y naves espaciales */ }; void Collision_SA ( void ) { /* manejar la colisión nave espacial-asteroide */ }; void colisión_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 una cantidad de cosas */ } Cosa ;         CollisionCase casos de colisión [ COSA_COUNT ][ COSA_COUNT ] = { { & colisión_AA , & colisión_AS }, { & colisión_SA , & colisión_SS } };       colisión de vacío ( Cosa a , Cosa b ) { ( * casos de colisión [ a ][ b ])(); }      int main ( vacío ) { colisionar ( THING_SPACESHIP , THING_ASTEROID ); }    

Con la biblioteca C Object System, [19] C admite el despacho dinámico similar a CLOS. Es totalmente 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:

#incluye <stdio.h> #incluye <cos/Object.h> #incluye <cos/gen/object.h>   // clasesdefclass ( Asteroide ) // miembros de datos clase final defclass ( nave espacial ) // miembros de datos clase final // genéricosdefgeneric ( _Bool , colisionar_con , _1 , _2 );    // multimétodosdefmethod ( _Bool , collide_with , Asteroid , Asteroid ) // lidiar con el asteroide que choca con el asteroide endmethod     defmethod ( _Bool , collide_with , asteroide , nave espacial ) // lidiar con el asteroide que golpea la nave espacial endmethod     defmethod ( _Bool , colisionar_con , nave espacial , asteroide ) // tratar con una nave espacial que choca contra un asteroide endmethod     defmethod ( _Bool , collide_with , Spaceship , Spaceship ) // lidiar con la nave espacial que golpea la nave espacial endmethod     // ejemplo de usoint main ( vacío ) { OBJ a = gnew ( Asteroide ); OBJ s = gnew ( nave espacial );          printf ( "<a,a> = %d \n " , colisiona_con ( a , a )); printf ( "<a,s> = %d \n " , colisiona_con ( a , s )); printf ( "<s,a> = %d \n " , colisiona_con ( s , a )); printf ( "<s,s> = %d \n " , colisionar_con ( s , s ));            liberación ( a ); liberación ( s ); } 

C++

A partir de 2021 , C++ admite de forma nativa solo el envío único, aunque Bjarne Stroustrup (y sus colaboradores) propusieron agregar métodos múltiples (despacho múltiple) en 2007. [20] Los métodos para solucionar este límite son análogos: use el patrón de visitante , reparto dinámico o una biblioteca:

 // Ejemplo de comparación de tipos de tiempo de ejecución a través dedynamic_cast struct Thing { virtual void choca con ( Cosa y otros ) = 0 ; };          struct Asteroid : Thing { void collideWith ( Thing & other ) { // la transmisión dinámica a un tipo de puntero devuelve NULL si la conversión falla // (la transmisión dinámica a un tipo de referencia generaría una excepción en caso de falla) if ( auto asteroide = transmisión_dinámica < Asteroide * > ( y otros )) { // manejar la colisión asteroide-asteroide } else if ( auto spaceship = dynamic_cast < nave espacial *> ( y otros )) { // manejar la colisión asteroide-nave espacial } else { // manejo predeterminado de colisiones aquí } } };                                  estructura Nave espacial : Cosa { void colisiona con ( Cosa y otros ) { if ( auto asteroide = dynamic_cast < Asteroide *> ( y otros )) { // manejar la colisión nave espacial-asteroide } else if ( auto spaceship = dynamic_cast < Nave espacial *> ( & other )) { // manejar la colisión entre naves espaciales } else { // manejo predeterminado de colisiones aquí } } };                               

o tabla de búsqueda de puntero a método:

#include <cstdint> #include <typeinfo> #include <unordered_map>   clase Cosa { protegida : Cosa ( std :: uint32_t cid ) : tid ( cid ) {} ​​const std :: uint32_t tid ; // escriba identificación             typedef void ( Cosa ::* CollisionHandler )( Cosa y otros ); typedef std :: unordered_map < std :: uint64_t , CollisionHandler > CollisionHandlerMap ;        addHandler vacío estático ( std :: uint32_t id1 , std :: uint32_t id2 , controlador CollisionHandler ) { CollisionCases . insertar ( CollisionHandlerMap :: valor_tipo ( clave ( id1 , id2 ), controlador )); } static std :: uint64_t key ( std :: uint32_t id1 , std :: uint32_t id2 ) { return std :: uint64_t ( id1 ) << 32 | id2 ; }                           Casos de colisión estáticos CollisionHandlerMap ;   public : void collideWith ( Cosa y otra ) { auto handler = colisionCases . buscar ( clave ( tid , otro . tid )); if ( controlador ! = casosdecolisión . fin ()) { ( este ->* controlador -> segundo ) ( otro ); // llamada de puntero a método } else { // manejo de colisiones predeterminado } } };                      clase Asteroide : Cosa pública { void asteroid_collision ( Cosa y otros ) { /*manejar la colisión asteroide-asteroide*/ } void spaceship_collision ( Cosa y otros ) { /*manejar la colisión asteroide-nave espacial*/ }                público : Asteroide () : Cosa ( cid ) {} ​​initCases estático vacío (); estática constante std :: uint32_t cid ; };          clase Nave espacial : Cosa pública { void asteroid_collision ( Cosa y otros ) { /*manejar colisión nave espacial-asteroide*/ } void spaceship_collision ( Cosa y otros ) { /*manejar colisión nave espacial-nave espacial*/ }               público : Nave espacial () : Cosa ( cid ) {} ​​static void initCases (); estática constante std :: uint32_t cid ; // identificador de clase };           Cosa :: CollisionHandlerMap Cosa :: colisionCases ; const std :: uint32_t Asteroide :: cid = typeid ( Asteroide ). código hash (); const std :: uint32_t Nave espacial :: cid = typeid ( Nave espacial ). código hash ();         void Asteroid::initCases () { addHandler ( cid , cid , CollisionHandler ( & Asteroid :: asteroid_collision )); addHandler ( cid , Nave espacial :: cid , CollisionHandler ( & Asteroide :: nave espacial_colisión )); }        nave espacial vacía ::initCases () { addHandler ( cid , asteroide :: cid , CollisionHandler ( & nave espacial :: asteroid_collision )); addHandler ( cid , cid , CollisionHandler ( & nave espacial :: nave espacial_colisión )); }        int main () { Asteroide :: initCases (); Nave espacial :: initCases ();     Asteroide a1 , a2 ; Nave espacial s1 , s2 ;      a1 . colisionar con ( a2 ); a1 . colisionar con ( s1 );  s1 . colisionar con ( s2 ); s1 . colisionar con ( a1 ); } 

La biblioteca YOMM2 [21] proporciona una implementación ortogonal rápida de multimétodos 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 del 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 la cantidad de argumentos virtuales y se pueden mezclar arbitrariamente con argumentos no virtuales.

La biblioteca utiliza una combinación de técnicas (tablas de distribución comprimidas, tabla hash de enteros sin colisiones) para implementar llamadas a métodos en tiempo constante, al tiempo que mitiga el uso de memoria. Enviar una llamada a un método abierto con un único argumento virtual lleva sólo entre un 15% y un 30% más de tiempo que llamar a una función miembro virtual ordinaria, cuando se utiliza un compilador de optimización moderno.

El ejemplo de Asteroides se puede implementar de la siguiente manera:

#include <yorel/yomm2/keywords.hpp> #include <memoria>  clase Cosa { público : virtual ~ Cosa () {} };      clase Asteroide : cosa pública { };     clase Nave espacial : cosa pública { };     Register_classes ( Cosa , Nave Espacial , Asteroide );  declarar_metodo ( void , colisionar con , ( virtual_ < cosa &> , virtual_ < cosa &> ));   define_method ( void , colisionar con , ( cosa e izquierda , cosa y derecha )) { // manejo de colisiones predeterminado }       define_method ( void , colisionar con , ( asteroide e izquierda , asteroide y derecha )) { // manejar la colisión asteroide-asteroide }       define_method ( void , colisionar con , ( asteroide e izquierda , nave espacial y derecha )) { // manejar la colisión asteroide-nave espacial }       define_method ( void , colisionar con , ( nave espacial e izquierda , asteroide y derecha )) { // manejar la colisión nave espacial-asteroide }       define_method ( void , colisionar con , ( nave espacial e izquierda , nave espacial y derecha )) { // manejar la colisión nave espacial-nave espacial }       int main () { yorel :: yomm2 :: update_methods ();    std :: Unique_ptr < Cosa > a1 ( std :: make_unique < Asteroide > ()), a2 ( std :: make_unique < Asteroide > ()); std :: Unique_ptr < Cosa > s1 ( std :: make_unique < Nave espacial > ()), s2 ( std :: make_unique < Nave espacial > ()); // nota: tipos parcialmente borrados       colisionar con ( * a1 , * a2 ); // Colisión asteroide-asteroide colisiona con ( * a1 , * s1 ); // Colisión asteroide-nave espacial colisiona con ( * s1 , * a1 ); // Colisión nave espacial-asteroide colisiona con ( * s1 , * s2 ); // Colisión nave espacial-nave espacial            devolver 0 ; } 

Stroustrup menciona en The Design and Evolution of C++ que le gustó el concepto de multimétodos y consideró implementarlo en C++, pero afirma haber sido incapaz de encontrar una implementación de muestra eficiente (comparable a funciones virtuales) y resolver algunos posibles problemas de ambigüedad de tipos. Luego afirma que, aunque sería bueno tener la característica, se puede implementar aproximadamente usando despacho 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. [22]

D

A partir de 2021 , al igual que muchos otros lenguajes de programación orientados a objetos, D admite de forma nativa solo el envío único. Sin embargo, es posible emular multimétodos abiertos como una función de biblioteca en D. La biblioteca openmethods [23] es un ejemplo.

// Declaración Matriz plus ( virtual ! Matriz , virtual ! Matriz );  // La anulación para dos objetos DenseMatrix @method Matrix _plus ( DenseMatrix a , DenseMatrix b ) { const int nr = a . filas ; constante int nc = a . columnas ; afirmar ( a . nr == b . nr ); afirmar ( a . nc == b . nc ); resultado automático = nueva DenseMatrix ; resultado . nr = nr ; resultado . nc = nc ; resultado . elementos . longitud = a . elementos . longitud ; resultado . elementos [] = a . elementos [] + b . elementos []; resultado de retorno ; }                                         // La anulación para dos objetos DiagonalMatrix @method Matrix _plus ( DiagonalMatrix a , DiagonalMatrix b ) { afirmar ( a . filas == b . filas ); doble [] suma ; suma . longitud = a . elementos . longitud ; suma [] = a . elementos [] + b . elementos []; devolver nueva DiagonalMatrix ( suma ); }                    

Java

En un lenguaje con un solo envío único, como Java , se puede emular el envío múltiple con múltiples niveles de envío único:

Clase UML Java despacho único.svg

interfaz  colisionable { void colisionar con ( final colisionable otro );      /* Estos métodos necesitarían nombres diferentes en un idioma sin sobrecarga de métodos. */ colisión de vacío con ( asteroide asteroide final ); colisión vacía con ( nave espacial final de la nave espacial ); }        class  Asteroid implements Collideable { public void collideWith ( final Collideable other ) { // Llama a collideWith en el otro objeto. otro . colisionar con ( esto ); }             colisión de vacío públicoCon ( asteroide final asteroide ) { // Manejar la colisión asteroide-asteroide. }        colisión de vacío públicoCon ( nave espacial final ) { // Manejar la colisión entre asteroide y nave espacial. } }       class  Spaceship implements Collideable { public void collideWith ( final Collideable other ) { // Llama a collideWith en el otro objeto. otro . colisionar con ( esto ); }             colisión de vacío público con ( asteroide final ) { // Manejar la colisión entre nave espacial y asteroide. }        public void colisiona con ( final nave espacial nave espacial ) { // Maneja la colisión nave espacial-nave espacial. } }       

instanceofTambién se pueden utilizar comprobaciones de tiempo de ejecución en uno o ambos niveles.

Soporte en lenguajes de programación.

Paradigma primario

Compatible con multimétodos generales

A través de extensiones

Ver también

Referencias

  1. ^ 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.
  2. ^ abcdefghijk 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, Tennessee, Estados Unidos: ACM. págs. 563–582. doi :10.1145/1449764.1449808. ISBN 9781605582153. S2CID  7605233.
  3. ^ ABCDE 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.
  4. ^ Castaña, 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 .
  5. ^ Castaña, Giuseppe (1996). Programación orientada a objetos: una base unificada. Avances en Informática Teórica. Birkhäuser. pag. 384.ISBN 978-0-8176-3905-1.
  6. ^ Castaña, 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. 
  7. ^ Bruce, Kim; Cardelli, Luca; Castaña, Giuseppe; Levaduras, Gary T.; Pierce, Benjamín (1995). "Sobre métodos binarios". Teoría y práctica de los sistemas de objetos . 1 (3): 221–242. doi :10.1002/j.1096-9942.1995.tb00019.x . Consultado el 19 de abril de 2013 .
  8. ^ "Uso de tipo dinámico (Guía de programación de C#)" . Consultado el 14 de mayo de 2020 .
  9. ^ "Conceptos básicos" . Consultado el 14 de mayo de 2020 .
  10. ^ "Dynamic .NET: comprensión de la palabra clave dinámica en C# 4" . Consultado el 14 de mayo de 2020 .
  11. ^ Groovy - Múltiples métodos
  12. ^ @arrows/multimethod Envío múltiple en JavaScript/TypeScript con resolución de envío configurable por Maciej Cąderek.
  13. ^ Coady, Aric, multimétodo: envío de múltiples argumentos. , recuperado el 28 de enero de 2021
  14. ^ multimethods.py Archivado el 9 de marzo de 2005 en Wayback Machine , envío múltiple en Python con resolución de envío configurable por David Mertz y otros.
  15. ^ "Múltiples métodos de cinco minutos en Python".
  16. ^ "PEAK-Reglas 0.5a1.dev". Índice de paquetes de Python . Consultado el 21 de marzo de 2014 .
  17. ^ "PyProtocolos". Kit de aplicaciones empresariales Python . Consultado el 26 de abril de 2019 .
  18. ^ "Registro". Lea los documentos . Consultado el 26 de abril de 2019 .
  19. ^ "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". GitHub . 2019-02-19.
  20. ^ "Informe sobre soporte de lenguajes 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 a 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.
  21. ^ yomm2, Múltiples métodos abiertos, ortogonales y rápidos para C++ por Jean-Louis Leroy.
  22. ^ Stroustrup, Bjarne (1994). "Sección 13.8". El diseño y evolución de C++ . Indianápolis, IN, EE.UU.: Addison Wesley. Bibcode : 1994dic..libro.....S. ISBN 978-0-201-54330-8.
  23. ^ openmethods, Métodos múltiples abiertos para D por Jean-Louis Leroy.
  24. ^ "Métodos". El manual de Julia . Julialang. Archivado desde el original el 17 de julio de 2016 . Consultado el 11 de mayo de 2014 .
  25. ^ "Multimétodos en C# 4.0 con 'Dynamic'" . Consultado el 20 de agosto de 2009 .
  26. ^ "Lenguaje Cecil" . Consultado el 13 de abril de 2008 .
  27. ^ "Multimétodos en Clojure" . Consultado el 4 de septiembre de 2008 .
  28. ^ Steele, Guy L. (1990). "28". LISP común: el lenguaje . Bedford, MA, EE.UU.: Prensa digital. ISBN 978-1-55558-041-4.
  29. ^ "Antecedentes y objetivos" . Consultado el 13 de abril de 2008 .
  30. ^ "La especificación del lenguaje Fortress, versión 1.0" (PDF) . Archivado desde el original (PDF) el 20 de enero de 2013 . Consultado el 23 de abril de 2010 .
  31. ^ "Múltiples métodos en Groovy" . Consultado el 13 de abril de 2008 .
  32. ^ "Métodos - LassoGuide 9.2" . Consultado el 11 de noviembre de 2014 .
  33. ^ "Patrón de visitantes versus multimétodos" . Consultado el 13 de abril de 2008 .
  34. ^ "Manual de Nim: métodos múltiples" . Consultado el 3 de mayo de 2022 .
  35. ^ "Preguntas frecuentes sobre Perl 6" . Consultado el 13 de abril de 2008 .
  36. ^ "Cómo funcionan los métodos S4" (PDF) . Consultado el 13 de abril de 2008 .
  37. ^ "Envío múltiple en Seed7" . Consultado el 23 de abril de 2011 .
  38. ^ "Manual del sistema TADS 3" . Consultado el 19 de marzo de 2012 .
  39. ^ "Envío múltiple de VB.Net" . Consultado el 31 de marzo de 2020 .
  40. ^ "Nuevas funciones en C#4.0 y VB.Net 10.0". 4 de noviembre de 2010 . Consultado el 31 de marzo de 2020 .
  41. ^ "Notas para expertos en lenguajes de programación" . Consultado el 21 de agosto de 2016 .
  42. ^ "Envío múltiple".

enlaces externos