Saltar al contenido

Polimorfismo paramétrico vs polimorfismo ad-hoc

Solución:

Según el TAPL, §23.2:

El polimorfismo paramétrico (…) permite que una sola pieza de código se escriba “genéricamente”, utilizando variables en lugar de tipos reales, y luego instanciada con tipos particulares según sea necesario. Las definiciones paramétricas son uniformes: todas sus instancias se comportan igual. (…)

El polimorfismo ad-hoc, por el contrario, permite que un valor polimórfico muestre diferentes comportamientos cuando se “ve” en diferentes tipos. El ejemplo más común de polimorfismo ad-hoc es la sobrecarga, que asocia un solo símbolo de función con muchas implementaciones; el compilador (o el sistema de ejecución, dependiendo de si la resolución de sobrecarga es estática o dinámica) elige una implementación adecuada para cada aplicación de la función, en función de los tipos de argumentos.

Entonces, si considera las etapas sucesivas de la historia, Java oficial no genérico (también conocido como pre-J2SE 5.0, antes de septiembre de 2004) tenía polimorfismo ad-hoc, por lo que podría sobrecargar un método, pero no polimorfismo paramétrico, por lo que no podría ‘ t escribir un método genérico. Después podrías hacer ambas cosas, por supuesto.

En comparación, desde sus inicios en 1990, Haskell fue paramétricamente polimórfico, lo que significa que se podía escribir:

swap :: (A; B) -> (B; A)
swap (x; y) = (y; x)

donde A y B son variables de tipo se pueden instanciar para todos tipos, sin supuestos.

Pero no había ningún constructo preexistente que diera ad hoc polimorfismo, que pretende permitirle escribir funciones que se aplican a varios, pero no todo tipos. Las clases de tipos se implementaron como una forma de lograr este objetivo.

Te dejan describir un clase (algo parecido a una interfaz Java), dando la tipo de firma de las funciones que desea implementar para su tipo genérico. Entonces puede registrar algunos (y con suerte, varios) instancias coincidiendo con esta clase. Mientras tanto, puede escribir un método genérico como:

between :: (Ord a)  a -> a -> a -> Bool
between x y z = x ≤ y ^ y ≤ z

donde el Ord es la clase que define la función (_ ≤ _). Cuando se utilizan, (between "abc" "d" "ghi") es resuelto estáticamente para seleccionar el derecho ejemplo para cadenas (en lugar de, por ejemplo, enteros), exactamente en el momento en que lo haría la sobrecarga del método (de Java).

Puede hacer algo similar en Java con comodines acotados. Pero el La diferencia clave entre Haskell y Java en ese frente es que solo Haskell puede pasar el diccionario automáticamente: en ambos idiomas, dados dos casos de Ord T, decir b0 y b1, puedes construir una función f que los toma como argumentos y produce la instancia para el tipo de par (b0, b1), usando, digamos, el orden lexicográfico. Di ahora que te han dado (("hello", 2), ((3, "hi"), 5)). En Java tienes que recordar las instancias para string y inty pasar la instancia correcta (compuesta de cuatro aplicaciones de f!) con el fin de aplicar between a ese objeto. Haskell puede aplicar la composicionalidad y descubrir cómo construir la instancia correcta dadas solo las instancias básicas y la f constructor (esto se extiende a otros constructores, por supuesto).


Ahora, en cuanto a inferencia de tipo va (y esta probablemente debería ser una pregunta distinta), para ambos idiomas es incompleto, en el sentido de que siempre puedes escribir un sin anotar programa para el que el compilador no podrá determinar el tipo.

  1. para Haskell, esto se debe a que tiene un polimorfismo impredicativo (también conocido como de primera clase), por lo que la inferencia de tipos es indecidible. Tenga en cuenta que en ese punto, Java está limitado al polimorfismo de primer orden (algo en lo que Scala se expande).

  2. para Java, esto se debe a que admite subtipos contravariantes.

Pero esos lenguajes difieren principalmente en el rango de declaraciones de programa a las que se aplica la inferencia de tipo en la práctica, y en el importancia dada a la corrección de los resultados de inferencia de tipo.

  1. Para Haskell, la inferencia se aplica a todos los términos “no altamente polimórficos” y hace un esfuerzo serio para devolver resultados sólidos basados ​​en extensiones publicadas de un algoritmo conocido:

    • En esencia, la inferencia de Haskell se basa en Hindley-Milner, que le brinda resultados completos tan pronto como al inferir el tipo de una aplicación, tipo de variables (por ejemplo, el A y B en el ejemplo anterior) solo se puede instanciar con no polimórfico tipos (estoy simplificando, pero este es esencialmente el polimorfismo de estilo ML que puede encontrar en, por ejemplo, Ocaml).
    • un GHC reciente se asegurará de que se requiera una anotación de tipo solo para un enlace let o una abstracción λ que tenga un tipo que no sea Damas-Milner.
    • Haskell ha tratado de mantenerse relativamente cerca de este núcleo inferible incluso en sus extensiones más peludas (por ejemplo, GADT). En cualquier caso, las extensiones propuestas casi siempre vienen en un papel con una prueba de la exactitud de la inferencia de tipo extendido.
  2. Para Java, la inferencia de tipos se aplica en un moda mucho más limitada de todas formas :

    Antes del lanzamiento de Java 5, no había inferencia de tipos en Java. Según la cultura del lenguaje Java, el tipo de cada variable, método y objeto asignado dinámicamente debe ser declarado explícitamente por el programador. Cuando se introdujeron los genéricos (clases y métodos parametrizados por tipo) en Java 5, el lenguaje retuvo este requisito para variables, métodos y asignaciones. Pero la introducción de métodos polimórficos (parametrizados por tipo) dicta que (i) el programador proporciona los argumentos de tipo de método en cada sitio de llamada de método polimórfico o (ii) el lenguaje admite la inferencia de argumentos de tipo de método. Para evitar crear una carga administrativa adicional para los programadores, los diseñadores de Java 5 eligieron realizar una inferencia de tipo para determinar los argumentos de tipo. para llamadas a métodos polimórficos. (fuente, énfasis mío)

    El algoritmo de inferencia es esencialmente el de GJ, pero con una adición algo torpe de comodines como una ocurrencia tardía (tenga en cuenta que no estoy actualizado sobre las posibles correcciones realizadas en J2SE 6.0, sin embargo). La gran diferencia conceptual en el enfoque es que la inferencia de Java es local, en el sentido de que el tipo inferido de una expresión depende solo de las restricciones generadas a partir del sistema de tipos y de los tipos de sus subexpresiones, pero no del contexto.

    Tenga en cuenta que la línea de partido con respecto a la inferencia de tipo incompleta y, a veces, incorrecta es relativamente relajada. Según la especificación:

    Tenga en cuenta también que la inferencia de tipos no afecta la solidez de ninguna manera. Si los tipos inferidos no tienen sentido, la invocación producirá un error de tipo. El algoritmo de inferencia de tipos debe verse como una heurística, diseñada para funcionar bien en la práctica. Si no puede inferir el resultado deseado, se pueden usar parámetros de tipo explícito en su lugar.

Polimorfismo paramétrico significa que no nos importa el tipo, implementaremos la función de la misma manera para cualquier tipo. Por ejemplo, en Haskell:

length :: [a] -> Int
length [] = 0          
length (x:xs) = 1 + length xs

No nos importa cuál sea el tipo de elementos de la lista, solo nos importa cuántos hay.

Polimorfismo ad-hoc (también conocido como sobrecarga de métodos), sin embargo, significa que usaremos una implementación diferente dependiendo del tipo de parámetro.

Aquí hay un ejemplo en Haskell. Digamos que queremos definir una función llamada makeBreakfast.

Si el parámetro de entrada es Eggs, Quiero makeBreakfast para devolver un mensaje sobre cómo hacer huevos.

Si el parámetro de entrada es Pancakes, Quiero makeBreakfast para devolver un mensaje sobre cómo hacer panqueques.

Crearemos una clase de tipos llamada BreakfastFood que implementa el makeBreakfast función. La implementación de makeBreakfast será diferente dependiendo del tipo de entrada a makeBreakfast.

class BreakfastFood food where
  makeBreakfast :: food -> String

instance BreakfastFood Eggs where
  makeBreakfast = "First crack 'em, then fry 'em"

instance BreakfastFood Toast where
  makeBreakfast = "Put bread in the toaster until brown"

Según John Mitchell Conceptos en lenguajes de programación,

La diferencia clave entre el polimorfismo paramétrico y la sobrecarga (también conocido como polimorfismo ad-hoc) es que las funciones polimórficas paramétricas usan un algoritmo para operar con argumentos de muchos tipos diferentes, mientras que las funciones sobrecargadas pueden usar un algoritmo diferente para cada tipo de argumento.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *