El despacho múltiple o multimétodos es una característica de algunos lenguajes de programación en la que una función o método puede ser despachado dinámicamente en función del tipo en tiempo de ejecución (dinámico) o, en el caso más general, de algún otro atributo de más de uno de sus argumentos . [ 1 ] Esta es una generalización del polimorfismo de despacho simple, donde una llamada a una función o método se despacha dinámicamente en función del tipo derivado del objeto sobre el que se ha llamado al método. El despacho múltiple dirige el despacho dinámico a la función o método que implementa utilizando las características combinadas de uno o más argumentos.
Comprender el despacho
Los desarrolladores de software suelen organizar el código fuente en bloques con nombre, denominados subrutinas , procedimientos, subprogramas, funciones o métodos. El código de una función se ejecuta llamándola , es decir, ejecutando un fragmento de código que hace referencia a su nombre . Esto transfiere temporalmente el control a la función llamada; una vez finalizada la ejecución de la función, el control se devuelve a la instrucción del programa que realiza la llamada , la cual sigue a la referencia.
Los nombres de las funciones suelen elegirse de forma que describan su propósito. En ocasiones, es conveniente asignar el mismo nombre a varias funciones, a menudo porque realizan tareas conceptualmente similares, pero operan con diferentes tipos de datos de entrada. En estos casos, la referencia al nombre en la llamada a la función no es suficiente para identificar el bloque de código que se va a ejecutar. En su lugar, también se utilizan el número y el tipo de argumentos de la llamada a la función para seleccionar entre varias implementaciones.
En los 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 de manera especial y se utiliza para determinar cuál de las (potencialmente muchas) clases de métodos con ese nombre se debe aplicar. En muchos lenguajes, el argumento especial se indica sintácticamente; por ejemplo, varios lenguajes de programación colocan el argumento especial antes de un punto al hacer una llamada a un método: , de modo que produciría un rugido, mientras que produciría un chirrido.special.method(other, arguments, here)lion.sound()sparrow.sound()
En cambio, en lenguajes con despacho múltiple, el método seleccionado es simplemente aquel cuyos argumentos coinciden con el número y el tipo de la llamada a la función. No existe ningún argumento especial que determine la función o el método ejecutado en una llamada específica.
El despacho múltiple debe distinguirse de la sobrecarga de funciones , en la que la información de tipado estático, como el tipo declarado o inferido de un término (o el tipo base en un lenguaje con subtipado), se utiliza para determinar cuál de varias posibilidades se utilizará en un punto de llamada determinado. Esta determinación se realiza en tiempo de compilación o enlace (o en algún otro momento antes de que comience la ejecución del programa) y, posteriormente, permanece invariable para una implementación o ejecución determinada del programa. Muchos lenguajes, como C++, ofrecen una sobrecarga de funciones robusta, pero no permiten el despacho múltiple dinámico (C++ solo permite el despacho simple dinámico mediante el uso de funciones virtuales).
Tipos de datos
Al trabajar con lenguajes que pueden distinguir los tipos de datos en tiempo de compilación , se puede seleccionar entre las alternativas. La creación de funciones alternativas para la selección en tiempo de compilación se conoce generalmente como sobrecarga de funciones.
En los lenguajes de programación que posponen 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 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 generalmente multimétodos .
La asignación dinámica de llamadas a funciones conlleva cierto coste en tiempo de ejecución. En algunos lenguajes, la distinción entre sobrecarga y multimétodos puede difuminarse, ya que el compilador determina si se puede aplicar la selección en tiempo de compilación a una llamada a función determinada o si se requiere una asignación más lenta en tiempo de ejecución.
Asuntos
Existen varios problemas conocidos con el despacho dinámico, tanto simple como múltiple. Si bien muchos de estos problemas se resuelven para el despacho simple, 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 complejos en el caso del despacho múltiple.
Expresividad y modularidad
En la mayoría de los lenguajes de programación más populares, el código fuente se entrega y despliega en fragmentos de funcionalidad que aquí denominaremos paquetes ; la terminología específica para este concepto varía según el lenguaje. Cada paquete puede contener múltiples definiciones de tipo, valor y función; en lenguajes con un paso de compilación, los paquetes suelen compilarse por separado, 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 otros paquetes, y el programa completo constituye el cierre transitivo de la relación de dependencia.
El llamado problema de la expresión se refiere a la capacidad del código de un paquete dependiente para extender comportamientos (funciones o tipos de datos) definidos en un paquete base desde dentro de un paquete incluido, sin modificar el código fuente del paquete base. Los lenguajes de programación orientada a objetos tradicionales de despacho simple hacen que sea trivial agregar nuevos tipos de datos, pero no nuevas funciones; los lenguajes funcionales tradicionales tienden a tener el efecto contrario, y el despacho múltiple, si se implementa correctamente, permite ambos. Es deseable que una implementación de despacho múltiple tenga las siguientes propiedades:
- Es posible definir diferentes "casos" de un método múltiple desde diferentes paquetes sin modificar el código fuente de un paquete base.
- La inclusión de otro paquete en el programa no debería alterar el comportamiento de una llamada a múltiples métodos determinada, siempre que dicha llamada no utilice ningún tipo de dato definido en el paquete.
- Por el contrario, si un tipo de dato se define en un paquete determinado, y una extensión multimétodo que utiliza ese tipo también se define en el mismo paquete, y un valor de ese tipo se pasa (a través de una referencia de tipo base o a una función genérica) a otro paquete sin dependencia de este, y luego se invoca el multimétodo con ese valor como argumento, se debe emplear el caso multimétodo definido en el paquete que incluye el tipo. Dicho de otro modo, dentro de un programa determinado, el mismo multimétodo invocado con el mismo conjunto de argumentos debe resolverse en la misma implementación, independientemente de la ubicación del punto de llamada y de si una definición determinada está o no "dentro del ámbito" o es "visible" en el momento de la llamada al método.
Ambigüedad
En general, es deseable que para cualquier invocación de un método múltiple, exista como máximo un candidato "óptimo" entre los casos de implementación del método múltiple, y/o que, si no lo hay, esto se resuelva de manera predecible y determinista, incluyendo el fallo. El comportamiento no determinista es indeseable. Suponiendo un conjunto de tipos con una relación de subtipo 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 la primera son subtipos de todos los argumentos distribuidos dinámicamente especificados en la segunda, y al menos uno es un subtipo estricto. Con distribución simple y en ausencia de herencia múltiple , esta condición se satisface trivialmente, pero con distribución 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 es el subtipo en un caso, otro es el subtipo en el otro caso). Esto puede ocurrir particularmente si dos paquetes diferentes, que no dependen el uno 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 a ambos (posiblemente de forma indirecta) invoca el método múltiple utilizando argumentos de ambos paquetes.
Las posibles soluciones incluyen:
- Tratar cualquier llamada ambigua como un error. Esto podría detectarse en tiempo de compilación (o antes del despliegue), pero podría no detectarse hasta el tiempo de ejecución y producir un error en tiempo de ejecución.
- El orden de los argumentos, por ejemplo, se selecciona el caso con el primer argumento más específico, y los argumentos subsiguientes no se consideran para la resolución de ambigüedades a menos que el primer argumento sea insuficiente para resolver el problema.
- Construcción de otras reglas para resolver una ambigüedad en una u otra dirección. A veces, estas reglas pueden ser arbitrarias y sorprendentes. En las reglas para la resolución de sobrecarga estática en C++, por ejemplo, un tipo que coincide exactamente se considera, comprensiblemente, una mejor coincidencia que un tipo que coincide a través de una referencia de tipo base o un parámetro genérico (plantilla). Sin embargo, si las únicas coincidencias posibles son a través de un tipo base o un parámetro genérico, se prefiere el parámetro genérico sobre el tipo base, una regla que a veces produce un comportamiento inesperado.
Eficiencia
Es bien sabido que la implementación eficiente del despacho único, incluso en lenguajes de programación que se compilan por separado a código objeto y se enlazan con un enlazador de bajo nivel (que no tiene en cuenta el lenguaje), incluso de forma dinámica al cargar o iniciar el programa o incluso bajo la dirección del código de la aplicación, es una práctica común. El método " vtable ", desarrollado en C++ y otros lenguajes de programación orientada a objetos (donde cada clase tiene un array de punteros a funciones que corresponden a las funciones virtuales de esa clase), es casi tan rápido como una llamada a un método estático, requiriendo una sobrecarga de O(1) y solo una búsqueda de memoria adicional, 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 adapta al caso de despacho múltiple. (También depende del paradigma orientado a objetos en el que los métodos son características de las clases, no entidades independientes de ningún tipo de dato en particular).
La implementación eficiente del despacho múltiple sigue siendo un problema de investigación en curso.
Uso en la práctica
Para estimar la frecuencia de uso del despacho múltiple en la práctica, Muschevici et al. [ 2 ] estudiaron programas que utilizan despacho dinámico. Analizaron nueve aplicaciones, principalmente 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 % al 93 % restante de las funciones genéricas tienen un método concreto (sobreevaluador) 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 las funciones con más sobreevaluadores concretos.
El despacho múltiple se utiliza mucho más en Julia , donde el despacho múltiple fue un concepto de diseño central desde el origen del lenguaje: recopilando 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 utiliza 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 la tasa de despacho DRes el número promedio de métodos por función genérica; la tasa 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 por tipo por método (es decir, el número de argumentos que se despachan):
Teoría
La teoría de lenguajes de despacho múltiple fue desarrollada por primera vez por Castagna et al., al definir un modelo para funciones sobrecargadas con enlace tardío . [ 4 ] [ 5 ] Esto dio lugar a la primera formalización del problema de la varianza de tipos (covarianza y contravarianza) de los lenguajes orientados a objetos [ 6 ] y una solución al problema de los métodos binarios. [ 7 ]
Ejemplos
Para entender mejor la diferencia entre despacho múltiple y simple, veamos un ejemplo. Imaginemos un juego que incluye, entre sus objetos (visibles para el usuario), naves espaciales y asteroides. Cuando dos objetos chocan, el programa podría necesitar realizar acciones distintas según cuál haya impactado.
Lenguajes con despacho múltiple incorporado
DO#
C# introdujo soporte para multimétodos dinámicos en la versión 4 [ 8 ] (abril de 2010) mediante la palabra clave 'dynamic'. El siguiente ejemplo muestra multimétodos. Al igual que muchos otros lenguajes de tipado estático, C# también admite la sobrecarga de métodos estáticos. [ 9 ] Microsoft espera que los desarrolladores prefieran el tipado estático al dinámico en la mayoría de los casos. [ 10 ] La palabra clave 'dynamic' admite la interoperabilidad con objetos COM y lenguajes .NET de tipado dinámico.
![]()
El siguiente ejemplo utiliza características introducidas en C# 9 y C# 10.
usando la biblioteca estática ColliderLibrary ;Console.WriteLine ( Colisión ( nuevo asteroide ( 101 ) , nueva nave espacial ( 300 ))); Console.WriteLine ( Colisión ( nuevo asteroide ( 10 ) , nueva nave espacial ( 10 ) )); Console.WriteLine ( Colisión ( nuevo asteroide ( 101 ) , nueva nave espacial ( 10 ))) ;string Collide ( SpaceObject x , SpaceObject y ) => x.Size > 100 && y.Size > 100 ? " ¡ Gran explosión!" : CollideWith ( x as dynamic , y as dynamic ) ; // Despacho dinámico al método CollideWithclase ColliderLibrary { public static string CollideWith ( Asteroide x , Asteroide y ) => "a/a" ; public static string CollideWith ( Asteroide x , Nave espacial y ) => "a/s" ; public static string CollideWith ( Nave espacial x , Asteroide y ) => "s/a" ; public static string CollideWith ( Nave espacial x , Nave espacial y ) => "s/s" ; }Registro abstracto SpaceObject ( int Size ); registro Asteroid ( int Size ) : SpaceObject ( Size ); registro Spaceship ( int Size ) : SpaceObject ( Size );Producción:
¡Gran explosión! a/s s/sGenial
Groovy es un lenguaje de propósito general compatible con Java e interutilizable para la JVM , que, a diferencia de Java, utiliza enlace tardío/despacho múltiple. [ 11 ]
/* Implementación en Groovy del ejemplo de C# anterior. El enlace tardío funciona igual al usar métodos no estáticos o compilar clases/métodos estáticamente (anotación @CompileStatic) */ 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 )) } }clase Collider { static String collide ( SpaceObject x , SpaceObject y ) { ( x . size > 100 && y . size > 100 ) ? "big-boom" : collideWith ( x , y ) // Despacho dinámico al método collideWith }private static String colisionarCon ( Asteroide x , Asteroide y ) { "a/a" } private static String colisionarCon ( Asteroide x , Nave espacial y ) { "a/s" } private static String colisionarCon ( Nave espacial x , Asteroide y ) { "s/a" } private static String colisionarCon ( Nave espacial x , Nave espacial y ) { "s/s" } }clase SpaceObject { int size SpaceObject ( int size ) { this . size = size } }@InheritConstructors class Asteroid extends SpaceObject {} @InheritConstructors class Spaceship extends SpaceObject {}Lisp común
En un lenguaje con despacho múltiple, como Common Lisp , podría verse más o menos así (ejemplo de Common Lisp mostrado):
( defclass asteroid () (( tamaño :reader tamaño :initarg :size ))) ( defclass spaceship () (( tamaño :reader tamaño :initarg :size ))) ( defun space-object ( clase tamaño ) ( make-instance clase :size tamaño )); colisionar-con es una función genérica con despacho múltiple ( defmethod colisionar-con (( asteroide x ) ( asteroide y )) "a/a" ) ( defmethod colisionar-con (( asteroide x ) ( nave espacial y )) "a/s" ) ( defmethod colisionar-con (( nave espacial x ) ( asteroide y )) "s/a" ) ( defmethod colisionar-con (( nave espacial x ) ( nave espacial y )) "s/s" )( defun colisionar ( x y ) ( si ( y ( > ( tamaño x ) 100 ) ( > ( tamaño y ) 100 )) "gran explosión" ( colisionar con x y )))( print ( colisionar ( objeto espacial 'asteroide 101 ) ( objeto espacial 'nave espacial 300 ))) ( print ( colisionar ( objeto espacial 'asteroide 10 ) ( objeto espacial 'nave espacial 10 ))) ( print ( colisionar ( objeto espacial 'nave espacial 101 ) ( objeto espacial 'nave espacial 10 )))Lo mismo ocurre con los demás métodos. No se utilizan pruebas explícitas ni la conversión dinámica de tipos.
Ante la presencia de despacho múltiple, la idea tradicional de que los métodos se definen en clases y se contienen en objetos resulta menos atractiva: cada método que colisiona con los anteriores se asocia 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 igual que la invocación de funciones ordinarias, y los métodos se agrupan no 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 en Julia del ejemplo anterior podría verse así:
tipo abstracto SpaceObject finstruct Asteroide <: SpaceObject tamaño :: Int fin struct Nave espacial <: SpaceObject tamaño :: Int fincolisionar_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 . size > 100 && y . size > 100 ) ? "¡Gran explosión!" : colisionar_con ( x , y )Producción:
julia> colisionar ( Asteroide ( 101 ), Nave espacial ( 300 )) "¡Gran explosión!"julia> colisionar ( Asteroide ( 10 ), Nave espacial ( 10 )) "a/s"julia> colisionar ( Nave espacial ( 101 ), Nave espacial ( 10 )) "s/s"Raku
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 semántica potente del lado del usuario mediante el despacho múltiple.
Posee tanto multimétodos como multisubrutinas. Dado que la mayoría de los operadores son subrutinas, también cuenta con múltiples operadores despachados.
Además de las restricciones de tipo habituales, también cuenta con restricciones de ubicación que permiten crear subrutinas muy especializadas.
subconjunto Masa de Real donde 0 ^..^ Inf ; rol Objeto-estelar { tiene Masa $.mass es requerido ; nombre del método () devuelve Str {...}; } clase Asteroide hace Stellar-Object { nombre del método () { 'un asteroide' } } class Spaceship does Stellar-Object { has Str $.name = 'some unnamed spaceship' ; } mi Str @destroyed = < obliterado destruido destrozado >; mi Str @damaged = « dañado 'colisionó 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 que los objetos se conviertan a un tipo Numérico. # (Si se convirtieran, no necesariamente necesitaríamos agregar estos operadores). # También podríamos haber definido operadores completamente nuevos de esta misma manera. 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 . masa } multi sub infijo: « == » ( Stellar-Object:D $a , Stellar-Object:D $b ) { $a . masa == $b . masa } # Define un nuevo despachador múltiple y agrega algunas restricciones de tipo a los parámetros. # Si no lo hubiéramos definido, habríamos obtenido uno genérico sin restricciones. proto sub collide ( Stellar-Object:D $, Stellar-Object:D $ ) {*} # No es necesario repetir los tipos aquí ya que son los mismos que el prototipo. # La restricción 'where' técnicamente solo se aplica a $b, no a toda la firma. # Tenga en cuenta que la restricción 'where' utiliza el candidato a operador `<` que agregamos anteriormente. multi sub collide ( $a , $b where $a < $b ) { say "$a.name() fue @destroyed.pick() por $b.name()" ; } multi sub collide ( $a , $b where $a > $b ) { # redistribuir al candidato anterior con los argumentos intercambiados samewith $b , $a ; } # Esto tiene que ir después de los dos primeros porque los otros # tienen restricciones 'where', que se comprueban en el # orden en que se escribieron las subrutinas. (Esta siempre coincidiría.) multi sub collide ( $a , $b ) { # aleatorizar el orden my ( $n1 , $n2 ) = ( $a . name , $b . name ). pick (*); say "$n1 @damaged.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 las naves tienen masas desiguales, se llama a uno de los dos primeros candidatos. multi sub collide ( Spaceship $a , Spaceship $b where $a == $b ){ my ( $n1 , $n2 ) = ( $a . name , $b . name ). pick (*); say "$n1 colisionó con $n2, y ambas naves fueron " , ( @destroyed . pick , 'dejó dañado' ). pick ; } # Puedes desempaquetar los atributos en variables dentro de la firma. # Incluso podrías tener una restricción sobre ellos `(:mass($a) donde 10)`. multi sub collide ( Asteroide $ (: masa ( $a )), Asteroide $ (: masa ( $b )) ){ decir "dos asteroides colisionaron y se combinaron en un asteroide más grande de masa { $a + $b }" ; } mi nave espacial $Enterprise .= new (: masa ( 1 ),: nombre ( 'La Enterprise' )); colisionar asteroide . new (: masa ( .1 )), $Enterprise ; colisionar $Enterprise , nave espacial . new (: masa ( .1 )); colisionar $Enterprise , asteroide . new (: masa ( 1 )); colisionar $Enterprise , nave espacial . new (: masa ( 1 )); colisionar asteroide . new (: masa ( 10 )), asteroide . new (: masa ( 5 )); Ampliación de lenguajes con bibliotecas de despacho múltiple
JavaScript
En lenguajes que no admiten despacho múltiple a nivel de definición o sintaxis, a menudo es posible agregarlo mediante una extensión de biblioteca . JavaScript y TypeScript no admiten multimétodos a nivel sintáctico, pero es posible agregar despacho múltiple a través de una biblioteca. Por ejemplo, el paquete multimethod [ 12 ] proporciona una implementación de despacho múltiple y funciones genéricas.
Versión con tipado dinámico en JavaScript:
import { multi , method } from '@arrows/multimethod'clase Asteroide {} clase Nave espacial {}const colisionarCon = multi ( método ([ Asteroide , Asteroide ], ( x , y ) => { // lidiar con el asteroide que golpea al asteroide }), método ([ Asteroide , Nave espacial ], ( x , y ) => { // lidiar con el asteroide que golpea a la nave espacial }), método ([ Nave espacial , Asteroide ], ( x , y ) => { // lidiar con la nave espacial que golpea al asteroide }), método ([ Nave espacial , Nave espacial ], ( x , y ) => { // lidiar con la nave espacial que golpea a la nave espacial }), )Versión con tipado estático en TypeScript:
import { multi , method , Multi } from '@arrows/multimethod'clase Asteroide {} clase Nave espacial {}tipo CollideWith = Multi & { ( x : Asteroide , y : Asteroide ) : void ( x : Asteroide , y : Nave espacial ) : void ( x : Nave espacial , y : Asteroide ) : void ( x : Nave espacial , y : Nave espacial ) : void }const colideWith : CollideWith = multi ( method ([ Asteroid , Asteroid ], ( x , y ) => { // lidiar con el asteroide que golpea al asteroide }), method ([ Asteroid , Spaceship ], ( x , y ) => { // lidiar con el asteroide que golpea a la nave espacial }), method ([ Spaceship , Asteroid ], ( x , y ) => { // lidiar con la nave espacial que golpea al asteroide }), method ([ Spaceship , Spaceship ], ( x , y ) => { // lidiar con la nave espacial que golpea a la nave espacial }), )Pitón
Se puede agregar despacho múltiple 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 al estilo CLOS para Python sin cambiar la sintaxis subyacente ni las palabras clave del lenguaje.
from typing import Anyimport game_behaviors from game_objects import Asteroid , Spaceship from multimethods import Dispatchcolisionar : Dispatch = Dispatch ( ) colisionar.add_rule ( ( Asteroid , Spaceship ) , game_behaviors.as_func ) colisionar.add_rule ( ( Spaceship , Spaceship ) , game_behaviors.ss_func ) colisionar.add_rule ( ( Spaceship , Asteroid ) , game_behaviors.sa_func )def aa_func ( a : Any , b : Any ) -> None : """Comportamiento cuando un asteroide choca con otro asteroide.""" # ...define un nuevo comportamiento...colisionar . add_rule (( Asteroide , Asteroide ), aa_func )# ...más tarde... colisionar ( cosa1 , cosa2 )Funcionalmente, es muy similar al ejemplo de CLOS, pero la sintaxis es la convencional de Python.
Utilizando decoradores (introducidos desde Python 2.4), Guido van Rossum produjo una implementación de ejemplo de multimétodos [ 15 ] con una sintaxis simplificada:
@multimethod ( Asteroide , Asteroide ) def colisionar ( a : Asteroide , b : Asteroide ) -> Ninguno : """Comportamiento cuando un asteroide choca con otro asteroide.""" # ...definir nuevo comportamiento...@multimethod ( Asteroide , Nave espacial ) def colisionar ( a : Asteroide , b : Nave espacial ) -> Ninguno : """Comportamiento cuando un asteroide choca con una nave espacial.""" # ...definir nuevo comportamiento...# ... definir otras reglas multimétodo ...y luego procede a definir el decorador multimétodo.
El paquete PEAK-Rules proporciona despacho múltiple con una sintaxis similar al ejemplo anterior. [ 16 ] Posteriormente fue reemplazado por PyProtocols. [ 17 ]
La biblioteca Reg también admite despacho múltiple y por predicado. [ 18 ]
Con la introducción de sugerencias de tipo , es posible el despacho múltiple con una sintaxis aún más simple. Por ejemplo, usando plum-dispatch ,
desde envío de importación de ciruelas@dispatch def collide ( a : Asteroide , b : Asteroide ) -> None : """Comportamiento cuando un asteroide choca con otro asteroide.""" # ...define un nuevo comportamiento...@dispatch def collide ( a : Asteroide , b : Nave espacial ) -> None : """Comportamiento cuando un asteroide choca con una nave espacial.""" # ...define nuevo comportamiento...# ...definir reglas adicionales...Emulando despacho múltiple
do
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 despacho dinámico se puede realizar buscando este valor en una tabla de ramificación de punteros a funciones . Aquí hay un ejemplo sencillo en C:
typedef void ( * CollisionCase )( void );void collisionAsteroidAsteroid ( void ) { // manejar la colisión asteroide-asteroide... }void collisionAsteroidSpaceship ( void ) { // manejar la colisión asteroide-nave espacial... }void collisionSpaceshipAsteroid ( void ) { // manejar la colisión entre la nave espacial y el asteroide... }void collisionSpaceshipSpaceship ( void ) { // manejar la colisión entre naves espaciales... }typedef enum { COLLIDEABLE_ASTEROID = 0 , COLLIDEABLE_SPACESHIP , COLLIDEABLE_COUNT // no es un tipo de Collideable en sí mismo, sino que se utiliza para encontrar el número de objetos espaciales definidos } Collideable ;CollisionCase collisionCases [ COLLIDEABLE_COUNT ][ COLLIDEABLE_COUNT ] = { { & collisionAsteroidAsteroid , & collisionAsteroidSpaceship }, { & collisionSpaceshipAsteroid , & collisionSpaceshipSpaceship } };void colisionar ( Collideable a , Collideable b ) { ( * colisionCases [ a ][ b ])(); }int main ( void ) { collide ( COLLIDEABLE_SPACESHIP , COLLIDEABLE_ASTEROID ); }Con la biblioteca C Object System, [ 19 ] C admite el despacho dinámico similar a CLOS. Es totalmente extensible y no requiere ningún manejo manual de los métodos. Los mensajes dinámicos (métodos) son despachados por el despachador de COS, que es más rápido que Objective-C . Aquí hay un ejemplo en COS:
#include <stdio.h> #include <cos/Object.h> #include <cos/gen/object.h>// clasesdefclass ( Asteroide ) // miembros de datos endclassdefclass ( Spaceship ) // miembros de datos endclass// genéricosdefgeneric ( bool , colisionar_con , _1 , _2 );// multimétodosdefmethod ( bool , collide_with , Asteroid , Asteroid ) // manejar el impacto del asteroide endmethoddefmethod ( bool , collide_with , Asteroid , Spaceship ) // Gestionar el impacto del asteroide contra la nave espacial endmethoddefmethod ( bool , collide_with , Spaceship , Asteroid ) // Gestionar el choque de la nave espacial con el asteroide endmethoddefmethod ( bool , collide_with , Spaceship , Spaceship ) // maneja el choque de naves espaciales endmethod// ejemplo de usoint main ( void ) { OBJ a = gnew ( Asteroide ); OBJ s = gnew ( Nave espacial );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 2021C ++ admite de forma nativa solo despacho único, aunque Bjarne Stroustrup (y colaboradores) propusieron en 2007 agregar múltiples métodos (despacho múltiple). [ 20 ] Los métodos para sortear este límite son análogos: usar el patrón Visitor , la conversión dinámica o una biblioteca:
// Ejemplo que utiliza la comparación de tipos en tiempo de ejecución mediante dynamic_castclase Colisionable { público : virtual void colisionarCon ( Colisionable & otro ) = 0 ; };clase Asteroide : public Colisionable { public : void colisionarCon ( Colisionable & otro ) { // dynamic_cast a un tipo puntero devuelve nullptr si la conversión falla // (dynamic_cast a un tipo de referencia lanzaría una excepción en caso de fallo) if ( Asteroide * asteroide = dynamic_cast < Asteroide *> ( & otro )) { // manejar la colisión Asteroide-Asteroide } else if ( Nave espacial * nave espacial = dynamic_cast < Nave espacial *> ( & otro )) { // manejar la colisión Asteroide-Nave espacial } else { // manejo de colisión predeterminado aquí } } };clase Spaceship : public Collideable { public : void collideWith ( Collideable & other ) { if ( Asteroid * asteroid = dynamic_cast < Asteroid *> ( & other )) { // manejar la colisión Spaceship-Asteroid } else if ( Spaceship * spaceship = dynamic_cast < Spaceship *> ( & other )) { // manejar la colisión Spaceship-Spaceship } else { // manejo de colisión predeterminado aquí } } };o tabla de búsqueda de puntero a método :
importar std ;usando std :: unordered_map ;clase Collideable { protected : explicit Collideable ( uint32_t cid ) : tid { cid } {}virtual ~ Collideable () = predeterminado ;const uint32_t tid ; // tipo id;using CollisionHandler = void ( Collideable ::* )( Collideable & other ); using CollisionHandlers = unordered_map < uint64_t , CollisionHandler > ;static void addHandler ( uint32_t id1 , uint32_t id2 , CollisionHandler handler ) { collisionCases.insert ( CollisionHandlers :: value_type ( key ( id1 , id2 ) , handler )) ; }static uint64_t key ( uint32_t id1 , uint32_t id2 ) { return uint64_t ( id1 ) << 32 | id2 ; }static inline CollisionHandlers collisionCases {}; public : void collideWith ( Collideable & other ) { if ( auto handler = collisionCases . find ( key ( tid , other . tid )); handler != collisionCases . end ()) { ( this ->* handler -> second )( other ); // puntero a llamada de método } else { // manejo de colisiones predeterminado } } };clase Asteroide : público Colisionable { privado : void asteroidCollision ( Colisionable & otro ) { // manejar colisión asteroide-asteroide }void spaceshipCollision ( Collideable & other ) { // manejar la colisión asteroide-nave espacial } public : Asteroid () : Collideable ( cid ) {}~ Asteroide () = predeterminado ;static void initCases (); static inline const uint32_t cid = typeid ( Asteroid ) .hash_code (); };clase Spaceship : public Collideable { private : void asteroidCollision ( Collideable & other ) { // manejar la colisión entre la nave espacial y el asteroide }void spaceshipCollision ( Collideable & other ) { // manejar la colisión entre naves espaciales } public : Spaceship () : Collideable ( cid ) {}~ Nave espacial () = predeterminado ;static void initCases (); static inline const uint32_t cid = typeid ( Spaceship ). hash_code (); // class id };void Asteroid::initCases () { addHandler ( cid , cid , CollisionHandler ( & Asteroid :: asteroidCollision )); addHandler ( cid , Spaceship :: cid , CollisionHandler ( & Asteroid :: spaceshipCollision )); }void Spaceship::initCases () { addHandler ( cid , Asteroid :: cid , CollisionHandler ( & Spaceship :: asteroidCollision )); addHandler ( cid , cid , CollisionHandler ( & Spaceship :: spaceshipCollision )); }int main ( int argc , char * argv []) { Asteroide :: initCases (); Nave espacial :: initCases ();Asteroide a1 ; Asteroide a2 ; Nave espacial s1 ; Nave espacial s2 ;a1.collideWith ( a2 ) ; a1.collideWith ( s1 ) ;s1.collideWith ( s2 ) ; s1.collideWith ( a1 ) ; }La biblioteca YOMM2 [ 21 ] proporciona una implementación rápida y ortogonal de multimétodos abiertos.
La sintaxis para declarar métodos abiertos se inspira en una propuesta para una implementación nativa en 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 C++ inline ordinarias; se pueden sobrecargar y pasar por puntero. No hay límite en el número de argumentos virtuales, y estos se pueden combinar arbitrariamente con argumentos no virtuales.
La biblioteca utiliza una combinación de técnicas (tablas de despacho comprimidas, tabla hash de enteros sin colisiones ) para implementar llamadas a métodos en tiempo constante, a la vez que minimiza el uso de memoria. Despachar una llamada a un método abierto con un único argumento virtual requiere solo entre un 15 % y un 30 % más de tiempo que llamar a una función miembro virtual ordinaria, cuando se utiliza un compilador optimizador moderno.
El ejemplo de los asteroides se puede implementar de la siguiente manera:
#incluir <yorel/yomm2/keywords.hpp>importar std ;usando std :: unique_ptr ;clase Colisionable { público : virtual ~ Colisionable () = predeterminado ; };clase Asteroide : público Colisionable { // ... };clase Nave espacial : público Colisionable { // ... };registrar_clases ( Colisionable , Nave espacial , Asteroide );declare_method ( void , collideWith , ( virtual_ < Collideable &> , virtual_ < Collideable &> ));define_method ( void , collideWith , ( Collideable & left , Collideable & right )) { // manejo de colisiones predeterminado }define_method ( void , collideWith , ( Asteroid & left , Asteroid & right )) { // manejar la colisión asteroide-asteroide }define_method ( void , collideWith , ( Asteroid & left , Spaceship & right )) { // manejar la colisión asteroide-nave espacial }define_method ( void , collideWith , ( Spaceship & left , Asteroid & right )) { // manejar la colisión entre la nave espacial y el asteroide }define_method ( void , collideWith , ( Spaceship & left , Spaceship & right )) { // manejar la colisión entre naves espaciales }int main ( int argc , char * argv []) { yorel :: yomm2 :: update_methods ();unique_ptr < Collideable > a1 ( std :: make_unique < Asteroid > ()); unique_ptr < Collideable > a2 ( std :: make_unique < Asteroid > ()); unique_ptr < Collideable > s1 ( std :: make_unique < Spaceship > ()); unique_ptr < Collideable > s2 ( std :: make_unique < Spaceship > ()); // nota: tipos parcialmente borradoscolisionarCon ( * a1 , * a2 ); // Colisión asteroide-asteroide colisionarCon ( * a1 , * s1 ); // Colisión asteroide-nave espacial colisionarCon ( * s1 , * a1 ); // Colisión nave espacial-asteroide colisionarCon ( * s1 , * s2 ); // Colisión nave espacial-nave espacialdevolver 0 ; }Stroustrup menciona en *The Design and Evolution of C++* que le gustaba el concepto de multimétodos y consideró implementarlo en C++, pero afirma no haber podido encontrar una implementación de ejemplo eficiente (comparable a las funciones virtuales) ni resolver algunos posibles problemas de ambigüedad de tipos. Luego afirma que, si bien la característica seguiría siendo útil, se puede implementar aproximadamente utilizando 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 2021Al igual que muchos otros lenguajes de programación orientados a objetos, D admite de forma nativa solo despacho único. Sin embargo, es posible emular métodos múltiples abiertos como una función de biblioteca en D. La biblioteca openmethods [ 23 ] es un ejemplo.
// Declaración Matrix plus ( virtual ! Matrix , virtual ! Matrix );// Sobreescritura para dos objetos DenseMatrix @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 ; }// Sobreescritura para dos objetos DiagonalMatrix @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
En un lenguaje con despacho único, como Java , el despacho múltiple se puede emular con múltiples niveles de despacho único:
![]()
interface Collideable { void collideWith ( Collideable other );// Estos métodos necesitarían nombres diferentes en un lenguaje sin sobrecarga de métodos. void collideWith ( Asteroide asteroide ); void collideWith ( Nave espacial nave espacial ); }class Asteroid implements Collideable { public void collideWith ( Collideable other ) { // Llama a collideWith en el otro objeto. other . collideWith ( this ); }public void collideWith ( Asteroide asteroide ) { // Manejar la colisión asteroide-asteroide. }public void collideWith ( Spaceship spaceship ) { // Manejar la colisión asteroide-nave espacial. } }class Spaceship implements Collideable { public void collideWith ( Collideable other ) { // Llama a collideWith en el otro objeto. other . collideWith ( this ); }public void collideWith ( Asteroide asteroide ) { // Manejar la colisión entre la nave espacial y el asteroide. }public void collideWith ( Spaceship spaceship ) { // Manejar la colisión entre naves espaciales. } }instanceofTambién se pueden utilizar comprobaciones en tiempo de ejecución en uno o ambos niveles.
Soporte en lenguajes de programación
Paradigma primario
Compatibilidad con múltiples métodos generales
- C# 4.0 [ 25 ]
- Cecil [ 26 ]
- Clojure [ 27 ]
- Common Lisp (a través del Sistema de Objetos de Common Lisp ) [ 28 ]
- Dylan [ 29 ]
- Emacs Lisp (a través de cl-defmethod )
- Fortaleza [ 30 ]
- Genial [ 31 ]
- Lazo [ 32 ] [ 33 ]
- Nim , hasta la versión 0.19.x (a partir de la versión 0.20.0 es necesario pasar una bandera del compilador) [ 34 ]
- Raku [ 35 ]
- R [ 36 ]
- TADS [ 37 ]
- Visual Basic (.NET) (VB.NET) [ 38 ] mediante enlace tardío, también mediante .Net DLR [ 39 ]
- Lenguaje Wolfram [ 40 ] mediante coincidencia de patrones simbólicos
- Xtend [ 41 ]
Mediante extensiones
- Cualquier lenguaje de framework .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 las bibliotecas yomm2 , multimethods y omm )
- D (a través de la biblioteca openmethods )
- Factor (a través del vocabulario multimétodo estándar )
- Java (usando la extensión MultiJava )
- JavaScript (a través del paquete @arrows/multimethod )
- Perl (a través del módulo Class::Multimethods )
- Python (a través de PEAK-Rules , RuleDispatch , gnosis.magic.multimethods , PyMultimethods , multipledispatch o plum-dispatch )
- Racket (a través de multimethod-lib )
- Ruby (a través de la biblioteca The Multiple Dispatch Library , el paquete Multimethod y el paquete Vlx-Multimethods )
- Esquema (por ejemplo, a través de TinyCLOS )
- TypeScript (a través del paquete @arrows/multimethod )
Véase también
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.
- 1 2 3 4 5 6 7 8 9 10 11 Muschevici, Radu; Potanin, Alex; Tempero, Ewan; Noble, James (2008). "Despacho 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, EE. UU.: ACM. págs. 563–582 . doi : 10.1145/1449764.1449808 . ISBN 9781605582153. S2CID 7605233 .
- 1 2 3 4 5 Bezanson, Jeff; Edelman, Alan; Karpinski, Stefan; Shah, Viral B. (7 de febrero de 2017). "Julia: Un nuevo enfoque para la computación numérica". SIAM Review . 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" . Information and Computation . 117 (1): 115– 135. doi : 10.1006/inco.1995.1033 .
- ↑ Castagna, Giuseppe (1996). Programación orientada a objetos: una base unificada . Progress in Theoretical Computer Science. Birkhäuser. p. 384. ISBN 978-0-8176-3905-1.
- ↑ Castagna, Giuseppe (1995). "Covarianza y contravarianza: conflicto sin causa". ACM Transactions on Programming Languages and Systems . 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" . Theory and Practice of Object Systems . 1 (3): 221– 242. doi : 10.1002/j.1096-9942.1995.tb00019.x . Consultado el 19 de abril de 2013 .
- ↑ "Uso de tipos dinámicos (Guía de programación de C#)" . Consultado el 14 de mayo de 2020 .
- ↑ "Conceptos básicos" . Consultado el 14 de mayo de 2020 .
- ↑ "Dynamic .NET - Entendiendo la palabra clave Dynamic en C# 4" . 10 de agosto de 2015. Consultado el 14 de mayo de 2020 .
- ↑ Groovy - Métodos múltiples
- ↑ @arrows/multimethod Despacho múltiple en JavaScript/TypeScript con resolución de despacho configurable por Maciej Cąderek.
- ↑ Coady, Aric, multimethod: Despacho de múltiples argumentos. , consultado el 28/01/2021
- ↑ multimethods.py Archivado el 9 de marzo de 2005 en Wayback Machine , Despacho múltiple en Python con resolución de despacho configurable por David Mertz, et al.
- ↑ "Múltiples métodos en Python en cinco minutos" .
- ↑ "PEAK-Rules 0.5a1.dev" . Índice de paquetes de Python . Consultado el 21 de marzo de 2014 .
- ↑ "PyProtocols" . Python Enterprise Application Kit . Consultado el 26 de abril de 2019 .
- ↑ "Reg" . Lea la documentación . Consultado el 26 de abril de 2019 .
- ↑ "C Object System: Un marco que lleva a C al nivel de otros lenguajes de programación de alto nivel y más allá: CObjectSystem/COS" . GitHub . 19 de febrero de 2019.
- ↑ "Informe sobre la compatibilidad del lenguaje con Multi-Methods y Open-Methods para C++" (PDF) . 11 de marzo de 2007.
El despacho 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 , Métodos múltiples abiertos, rápidos y ortogonales 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 : 1994dic..book.....S . ISBN 978-0-201-54330-8.
- ↑ openmethods , Métodos múltiples abiertos para D por Jean-Louis Leroy.
- ↑ "Métodos" . El manual de Julia . Julialang. Archivado del original el 17 de julio de 2016. Recuperado el 11 de mayo de 2014 .
- ↑ "Multimétodos en C# 4.0 con 'Dynamic'"" . Consultado el 20 de agosto de 2009 .
- ↑ "Lenguaje Cecil" . Consultado el 13 de abril de 2008 .
- ↑ "Multimétodos en Clojure" . Consultado el 4 de septiembre de 2008 .
- ↑ Steele, Guy L. (1990). "28" . Common LISP: El lenguaje . Bedford, MA, EE. UU.: Digital Press. ISBN 978-1-55558-041-4.
- ↑ "Antecedentes y objetivos" . Consultado el 13 de abril de 2008 .
- ↑ "Especificación del lenguaje Fortress, versión 1.0" (PDF) . Archivado del original (PDF) el 20 de enero de 2013. Consultado el 23 de abril de 2010 .
- ↑ "Multimé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 multimétodos" . Consultado el 13 de abril de 2008 .
- ↑ "Manual de Nim: Métodos múltiples" . Consultado el 3 de mayo de 2022 .
- ↑ "Preguntas frecuentes sobre Perl 6" . Consultado el 13 de abril de 2008 .
- ↑ "Cómo funcionan los métodos S4" (PDF) . Consultado el 13 de abril de 2008 .
- ↑ "Manual del sistema TADS 3" . Consultado el 19 de marzo de 2012 .
- ↑ "VB.Net Multiple Dispatch" . Consultado el 31 de marzo de 2020 .
- ↑ "Nuevas características en C#4.0 y VB.Net 10.0" . 4 de noviembre de 2010. Consultado el 31 de marzo de 2020 .
- ↑ "Notas para expertos en lenguajes de programación" . Consultado el 21 de agosto de 2016 .
- ↑ "Despacho múltiple" .
Enlaces externos
- Stroustrup, Bjarne; Solodkyy, Yuriy; Pirkelbauer, Peter (2007). Métodos múltiples abiertos para C++ (PDF) . Sexta Conferencia Internacional de la 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 .
- Método (programación informática)
- Polimorfismo (informática)
- Comparación de lenguajes de programación