Interfaz de Java y clase de tipos de Haskell: ¿diferencias y similitudes?


112

Mientras aprendo Haskell, noté su clase de tipo , que se supone que es un gran invento que se originó en Haskell.

Sin embargo, en la página de Wikipedia sobre la clase de tipo :

El programador define una clase de tipos especificando un conjunto de nombres de funciones o constantes, junto con sus respectivos tipos, que deben existir para cada tipo que pertenece a la clase.

Lo que me parece bastante cercano a la interfaz de Java (citando la página de la interfaz de Wikipedia (Java) ):

Una interfaz en el lenguaje de programación Java es un tipo abstracto que se utiliza para especificar una interfaz (en el sentido genérico del término) que las clases deben implementar.

Estos dos se ven bastante similares: la clase de tipo limita el comportamiento de un tipo, mientras que la interfaz limita el comportamiento de una clase.

Me pregunto cuáles son las diferencias y similitudes entre la clase de tipos en Haskell y la interfaz en Java, o tal vez son fundamentalmente diferentes.

EDITAR: noté que incluso haskell.org admite que son similares . Si son tan similares (¿o lo son?), ¿Por qué la clase de tipos se trata con tanta exageración?

MÁS EDITAR: ¡ Vaya, tantas respuestas geniales! Supongo que tendré que dejar que la comunidad decida cuál es la mejor. Sin embargo, al leer las respuestas, todas parecen decir simplemente que "hay muchas cosas que la clase de tipos puede hacer mientras que la interfaz no puede o tiene que hacer frente a los genéricos" . No puedo evitar preguntarme, ¿hay algo que las interfaces puedan hacer mientras que las clases de tipos no? Además, noté que Wikipedia afirma que typeclass se inventó originalmente en el artículo de 1989 * "Cómo hacer que el polimorfismo ad-hoc sea menos ad hoc", mientras Haskell todavía está en su cuna, mientras que el proyecto Java se inició en 1991 y se lanzó por primera vez en 1995. Entonces, tal vez en lugar de que typeclass sea similar a las interfaces, es al revés, que las interfaces fueron influenciadas por typeclass.¿Hay algún documento / documentación que apoye o refute esto? Gracias por todas las respuestas, ¡todas son muy esclarecedoras!

¡Gracias por todas las entradas!


3
No, realmente no hay nada que las interfaces puedan hacer que las clases de tipos no puedan, con la principal advertencia de que las interfaces generalmente aparecen en lenguajes que tienen funciones integradas que no se encuentran en Haskell. Si se añadieran clases de tipos a Java, también podrían utilizar esas funciones.
CA McCann

8
Si tiene varias preguntas, debe hacer varias, no tratar de agruparlas todas en una sola pregunta. De todos modos, para responder a su última pregunta: la principal influencia de Java es Objective-C (y no C ++ como a menudo se informa falsamente), cuyas principales influencias a su vez son Smalltalk y C.Las interfaces de Java son una adaptación de los protocolos de Objective-C que nuevamente son un formalización de la idea de protocolo en OO, que a su vez se basa en la idea de protocolos en networking, en concreto el ARPANet. Todo esto sucedió mucho antes del artículo que citó. ...
Jörg W Mittag

1
... La influencia de Haskell en Java llegó mucho más tarde y se limita a los genéricos, que, después de todo, fueron co-diseñados por uno de los diseñadores de Haskell, Phil Wadler.
Jörg W Mittag

5
Este es un artículo de Usenet de Patrick Naughton, uno de los diseñadores originales de Java: Java fue fuertemente influenciado por Objective-C y no por C ++ . Desafortunadamente, es tan antiguo que la publicación original ni siquiera aparece en los archivos de Google.
Jörg W Mittag

8
Hay otra pregunta que se cerró como un duplicado exacto de esta, pero tiene una respuesta mucho más detallada: stackoverflow.com/questions/8122109/…
Ben

Respuestas:


50

Yo diría que una interfaz es como una clase de tipos SomeInterface t donde todos los valores tienen el tipo t -> whatever(donde whateverno contiene t). Esto se debe a que con el tipo de relación de herencia en Java y lenguajes similares, el método llamado depende del tipo de objeto en el que se invoca y nada más.

Eso significa que es muy difícil hacer cosas como add :: t -> t -> t con una interfaz, donde es polimórfica en más de un parámetro, porque no hay forma de que la interfaz especifique que el tipo de argumento y el tipo de retorno del método es del mismo tipo que el tipo de el objeto al que se llama (es decir, el tipo "self"). Con Generics, hay formas de falsificar esto creando una interfaz con un parámetro genérico que se espera que sea del mismo tipo que el objeto en sí, como cómo lo Comparable<T>hace, donde se espera que lo uses Foo implements Comparable<Foo>para que el compareTo(T otherobject)tipo de tenga tipo t -> t -> Ordering. Pero eso aún requiere que el programador siga esta regla, y también causa dolores de cabeza cuando la gente quiere hacer una función que use esta interfaz, tienen que tener parámetros recursivos de tipo genérico.

Además, no tendrá cosas como empty :: tporque no está llamando a una función aquí, por lo que no es un método.


1
Scala traits (básicamente interfaces) permite this.type para que pueda devolver o aceptar parámetros del "self type". Scala tiene una característica completamente separada que ellos llaman "tipos propios" por cierto que no tiene nada que ver con esto. Nada de esto es una diferencia conceptual, solo una diferencia de implementaciones.
arcilla

45

Lo que es similar entre interfaces y clases de tipos es que nombran y describen un conjunto de operaciones relacionadas. Las operaciones en sí mismas se describen mediante sus nombres, entradas y salidas. Asimismo, puede haber muchas implementaciones de estas operaciones que probablemente difieran en su implementación.

Con eso fuera del camino, aquí hay algunas diferencias notables:

  • Los métodos de interfaz siempre están asociados con una instancia de objeto. En otras palabras, siempre hay un parámetro 'this' implícito que es el objeto en el que se llama al método. Todas las entradas a una función de clase de tipo son explícitas.
  • Una implementación de interfaz debe definirse como parte de la clase que implementa la interfaz. A la inversa, una clase de tipo 'instancia' se puede definir completamente separada de su tipo asociado ... incluso en otro módulo.

En general, creo que es justo decir que las clases de tipos son más potentes y flexibles que las interfaces. ¿Cómo definiría una interfaz para convertir una cadena en algún valor o instancia del tipo de implementación? Ciertamente no es imposible, pero el resultado no sería intuitivo ni elegante. ¿Alguna vez ha deseado que fuera posible implementar una interfaz para un tipo en alguna biblioteca compilada? Ambos son fáciles de lograr con clases de tipos.


1
¿Cómo extenderías las clases de tipos? ¿Pueden las clases de tipos extender otras clases de tipos al igual que las interfaces pueden extender las interfaces?
CMCDragonkai

10
Podría valer la pena actualizar esta respuesta a la luz de las implementaciones predeterminadas de Java 8 en las interfaces.
Reintegro a Monica el

1
@CMCDragonkai Sí, por ejemplo, puede decir "class (Foo a) => Bar a donde ..." para especificar que la clase de tipo Bar extiende la clase de tipo Foo. Al igual que Java, Haskell tiene herencia múltiple aquí.
Reincorporación a Monica el

En este caso, ¿la clase de tipos no es la misma que los protocolos en Clojure pero con seguridad de tipos?
Nawfal

24

Las clases de tipos se crearon como una forma estructurada de expresar "polimorfismo ad-hoc", que es básicamente el término técnico para las funciones sobrecargadas . Una definición de clase de tipo se parece a esto:

class Foobar a where
    foo :: a -> a -> Bool
    bar :: String -> a

Lo que esto significa es que, cuando usa aplicar la función fooa algunos argumentos de un tipo que pertenecen a la clase Foobar, busca una implementación defoo específica para ese tipo y la usa. Esto es muy similar a la situación con la sobrecarga de operadores en lenguajes como C ++ / C #, excepto que es más flexible y generalizado.

Las interfaces tienen un propósito similar en los lenguajes OO, pero el concepto subyacente es algo diferente; Los lenguajes OO vienen con una noción incorporada de jerarquías de tipos que Haskell simplemente no tiene, lo que complica las cosas de alguna manera porque las interfaces pueden involucrar tanto la sobrecarga por subtipos (es decir, llamar a métodos en instancias apropiadas, subtipos que implementan interfaces que hacen sus supertipos) y por despacho basado en tipo plano (ya que dos clases que implementan una interfaz pueden no tener una superclase común que también la implemente). Dada la enorme complejidad adicional introducida por el subtipo, sugiero que es más útil pensar en las clases de tipos como una versión mejorada de funciones sobrecargadas en un lenguaje que no sea OO.

También vale la pena señalar que las clases de tipos tienen medios de distribución mucho más flexibles: las interfaces generalmente se aplican solo a la clase única que las implementa, mientras que las clases de tipos se definen para un tipo , que puede aparecer en cualquier lugar de la firma de las funciones de la clase. El equivalente de esto en interfaces OO sería permitir que la interfaz defina formas de pasar un objeto de esa clase a otras clases, definir métodos estáticos y constructores que seleccionarían una implementación basada en qué tipo de retorno se requiere en el contexto de llamada, definir métodos que tomar argumentos del mismo tipo que la clase que implementa la interfaz y varias otras cosas que realmente no se traducen en absoluto.

En resumen: tienen propósitos similares, pero la forma en que funcionan es algo diferente, y las clases de tipos son significativamente más expresivas y, en algunos casos, más simples de usar debido a que trabajan con tipos fijos en lugar de partes de una jerarquía de herencia.


Estaba luchando por comprender las jerarquías de tipos en Haskell, ¿qué opinas de los tipos de sistemas de tipos como Omega? ¿Podrían simular jerarquías de tipos?
CMCDragonkai

@CMCDragonkai: No estoy lo suficientemente familiarizado con Omega para decir realmente, lo siento.
CA McCann

16

He leído las respuestas anteriores. Siento que puedo responder un poco más claramente:

Una "clase de tipo" Haskell y una "interfaz" Java / C # o un "rasgo" Scala son básicamente análogos. No existe una distinción conceptual entre ellos, pero existen diferencias de implementación:

  • Las clases de tipos de Haskell se implementan con "instancias" que están separadas de la definición del tipo de datos. En C # / Java / Scala, las interfaces / rasgos deben implementarse en la definición de clase.
  • Las clases de tipo Haskell le permiten devolver este tipo o un tipo propio. Los rasgos de Scala también lo hacen (this.type). Tenga en cuenta que los "tipos propios" en Scala son una característica completamente independiente. Java / C # requiere una solución desordenada con genéricos para aproximarse a este comportamiento.
  • Las clases de tipo Haskell le permiten definir funciones (incluidas las constantes) sin una entrada "este" parámetro de tipo. Las interfaces Java / C # y los rasgos Scala requieren un parámetro de entrada "this" en todas las funciones.
  • Las clases de tipos de Haskell le permiten definir implementaciones predeterminadas para funciones. También lo hacen los rasgos de Scala y las interfaces de Java 8+. C # puede aproximarse a algo como esto con métodos de extensiones.

2
Solo para agregar algo de literatura a estos puntos de respuesta, leí On (Haskell) Type Classes and (C #) Interfaces hoy, que también se compara con las interfaces C # en lugar de Javas, pero debería dar una comprensión del lado conceptual diseccionando la noción de un interfaz a través de las fronteras del idioma.
daniel.kahlenberg

Creo que algunas aproximaciones para cosas como las implementaciones predeterminadas se hacen de manera bastante más eficiente en C # con clases abstractas, ¿quizás?
Arwin

12

En Mentes maestras de programación , hay una entrevista sobre Haskell con Phil Wadler, el inventor de las clases de tipos, quien explica las similitudes entre las interfaces en Java y las clases de tipos en Haskell:

Un método Java como:

   public static <T extends Comparable<T>> T min (T x, T y) 
   {
      if (x.compare(y) < 0)
            return x; 
      else
            return y; 
   }

es muy similar al método Haskell:

   min :: Ord a => a -> a -> a
   min x y  = if x < y then x else y

Entonces, las clases de tipos están relacionadas con interfaces, pero la correspondencia real sería un método estático parametrizado con un tipo como el anterior.


Esta debería ser la respuesta, porque según la página de inicio de Phil Wadler , él fue el diseñador principal de Haskell, al mismo tiempo que también diseñó la extensión Generics para Java, que luego se incluyó en el lenguaje mismo.
Wong Jia Hau


8

Lea Extensión de software e integración con clases de tipos donde se dan ejemplos de cómo las clases de tipos pueden resolver una serie de problemas que las interfaces no pueden.

Los ejemplos enumerados en el documento son:

  • el problema de la expresión,
  • el problema de integración del marco,
  • el problema de la extensibilidad independiente,
  • la tiranía de la descomposición dominante, la dispersión y el enredo.

3
Link está muerto arriba. Prueba este en su lugar .
Justin Leitgeb

1
Bueno, ahora ese también está muerto. Prueba este
RichardW

7

No puedo hablar con el nivel de "exageración", si así me parece bien. Pero sí, las clases de tipos son similares en muchos aspectos. Una diferencia en la que puedo pensar es que Haskell puede proporcionar comportamiento para algunas de las operaciones de la clase de tipo :

class  Eq a  where
  (==), (/=) :: a -> a -> Bool
  x /= y     = not (x == y)
  x == y     = not (x /= y)

que muestra que hay dos operaciones, iguales (==)y no iguales (/=), para cosas que son instancias de la Eqclase de tipos. Pero la operación no igual se define en términos de iguales (de modo que solo tendría que proporcionar uno) y viceversa.

Entonces en probablemente-no-legal-Java eso sería algo como:

interface Equal<T> {
    bool isEqual(T other) {
        return !isNotEqual(other); 
    }

    bool isNotEqual(T other) {
        return !isEqual(other); 
    }
}

y la forma en que funcionaría es que solo necesitaría proporcionar uno de esos métodos para implementar la interfaz. Entonces, diría que la capacidad de proporcionar una especie de implementación parcial del comportamiento que desea en el nivel de interfaz es una diferencia.


6

Son similares (léase: tienen un uso similar), y probablemente se implementan de manera similar: las funciones polimórficas en Haskell toman bajo el capó una 'vtable' que enumera las funciones asociadas con la clase de tipos.

Esta tabla a menudo se puede deducir en tiempo de compilación. Probablemente esto sea menos cierto en Java.

Pero esta es una tabla de funciones , no de métodos . Los métodos están vinculados a un objeto, las clases de tipos de Haskell no.

Véalos como los genéricos de Java.


3

Como dice Daniel, las implementaciones de interfaz se definen por separado de las declaraciones de datos. Y como han señalado otros, existe una forma sencilla de definir operaciones que utilizan el mismo tipo libre en más de un lugar. Entonces es fácil de definirNum como una clase de tipos. Por lo tanto, en Haskell obtenemos los beneficios sintácticos de la sobrecarga de operadores sin tener realmente operadores sobrecargados mágicamente, solo clases de tipos estándar.

Otra diferencia es que puede usar métodos basados ​​en un tipo, ¡incluso cuando aún no tiene un valor concreto de ese tipo!

Por ejemplo read :: Read a => String -> a,. Entonces, si tiene suficiente información de otro tipo sobre cómo usará el resultado de una "lectura", puede dejar que el compilador averigüe qué diccionario usar para usted.

También puede hacer cosas como las instance (Read a) => Read [a] where...que le permiten definir una instancia de lectura para cualquier lista de cosas legibles. No creo que eso sea posible en Java.

Y todo esto es solo clases de tipos estándar de un solo parámetro sin engaños. Una vez que introducimos las clases de tipos multiparamétricas, se abre un nuevo mundo de posibilidades, y más aún con las dependencias funcionales y las familias de tipos, que le permiten integrar mucha más información y cálculo en el sistema de tipos.


1
Las clases de tipos normales son para interfaces como las clases de tipos de parámetros múltiples son para despacho múltiple en OOP; obtienes un aumento correspondiente en el poder tanto del lenguaje de programación como del dolor de cabeza del programador.
CA McCann
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.