una que propiedades programacion orientada objetos metodo instancia ejemplos clases clase atributo oop haskell interface functional-programming typeclass

oop - que - Diferencia entre las interfaces de programación orientada a objetos y las clases de tipos de FP



que es una clase en programacion (4)

La diferencia principal, que hace que las clases de tipo sean mucho más flexibles que las interfaces, es que las clases de tipo son independientes de sus tipos de datos y pueden agregarse posteriormente . Otra diferencia (al menos para Java) es que puede proporcionar implementaciones predeterminadas. Un ejemplo:

//Java public interface HasSize { public int size(); public boolean isEmpty(); }

Tener esta interfaz es agradable, pero no hay forma de agregarla a una clase existente sin cambiarla. Si tiene suerte, la clase no es definitiva (por ejemplo, ArrayList ), por lo que podría escribir una subclase para implementar la interfaz. Si la clase es final (por ejemplo, String ) no tienes suerte.

Compare esto con Haskell. Puedes escribir la clase de tipo:

--Haskell class HasSize a where size :: a -> Int isEmpty :: a -> Bool isEmpty x = size x == 0

Y puede agregar tipos de datos existentes a la clase sin tocarlos:

instance HasSize [a] where size = length

Otra buena propiedad de type-classes es la invocación implícita. Por ejemplo, si tiene un Comparator en Java, debe pasarlo como valor explícito. En Haskell, el Ord equivalente se puede usar automáticamente, tan pronto como una instancia apropiada esté en el alcance.

Posible duplicado:
La interfaz de Java y la clase de tipos de Haskell: ¿diferencias y similitudes?

Cuando comencé a aprender haskell, me dijeron que las clases de tipos son más poderosas que / diferentes a las interfaces.

Un año después, he usado interfaces y clases de tipos extensivamente y aún no he visto un ejemplo o explicación de cómo son diferentes. No es una revelación natural, me he perdido algo obvio, o en realidad no hay una diferencia real.

Buscar en Internet no ha encontrado nada sustancial. Entonces, entonces, ¿tienes la respuesta?


Puedes ver esto desde múltiples ángulos. Otras personas estarán en desacuerdo, pero creo que las interfaces OOP son un buen lugar para comenzar a entender las clases de tipo (sin duda, en comparación a partir de nada).

A las personas les gusta señalar que, conceptualmente, las clases clasifican los tipos, al igual que los conjuntos: "el conjunto de tipos que admiten estas operaciones, junto con otras expectativas que no se pueden codificar en el idioma en sí". Tiene sentido y ocasionalmente se hace para declarar una clase de tipo sin métodos, diciendo "solo haz que tu tipo sea una instancia de esta clase si cumple con ciertos requisitos". Eso ocurre muy raramente con las interfaces OOP.

En términos de diferencias concretas, existen múltiples formas en que las clases de tipo son más poderosas que las interfaces OOP:

  • El más grande es que las clases de tipo desacoplan la declaración de que un tipo implementa una interfaz a partir de la declaración del tipo en sí. Con las interfaces OOP, enumera las interfaces que implementa un tipo cuando lo define, y no hay forma de agregar más más tarde. Con clases de tipos, si crea una nueva clase de tipo que un tipo dado "arriba de la jerarquía de módulos" podría implementar pero no conoce, puede escribir una declaración de instancia. Si tiene un tipo y una clase de tipo de terceros independientes que no se conocen entre sí, puede escribir una declaración de instancia. En casos análogos con interfaces OOP, en su mayor parte solo está bloqueado, aunque los lenguajes OOP han evolucionado en "patrones de diseño" (adaptador) para evitar la limitación.

  • El siguiente más grande (esto es subjetivo, por supuesto) es que, aunque conceptualmente, las interfaces OOP son un conjunto de métodos que se pueden invocar en objetos que implementan la interfaz, las clases de tipos son un conjunto de métodos que se pueden usar con tipos que son miembros de la clase. La distinción es importante. Como los métodos de clase de tipo se definen con referencia al tipo, en lugar del objeto, no hay ningún obstáculo para tener métodos con múltiples objetos del tipo como parámetros (operadores de igualdad y comparación), o que devuelven un objeto del tipo como resultado ( varias operaciones aritméticas), o incluso constantes del tipo (límite mínimo y máximo). Las interfaces de OOP simplemente no pueden hacer esto, y los lenguajes de OOP han evolucionado en los patrones de diseño (por ejemplo, el método de clonación virtual) para evitar la limitación.

  • Las interfaces OOP solo se pueden definir para tipos; las clases de tipos también se pueden definir para lo que se llama "constructores de tipos". Los diversos tipos de colecciones definidos utilizando plantillas y genéricos en los diversos lenguajes OOP derivados de C son constructores de tipos: List toma un tipo T como argumento y construye el tipo List <T>. Las clases de tipos le permiten declarar interfaces para constructores de tipos: por ejemplo, una operación de mapeo para tipos de colección que llama a una función proporcionada en cada elemento de una colección, y recoge los resultados en una nueva copia de la colección, ¡potencialmente con un tipo de elemento diferente! Nuevamente, no puede hacer esto con interfaces OOP.

  • Si un parámetro dado necesita implementar múltiples interfaces, con las clases de tipo es trivialmente fácil enumerar a cuáles debe pertenecer; con las interfaces OOP, solo puede especificar una única interfaz como el tipo de un puntero o referencia dados. Si necesita implementar más, sus únicas opciones son poco atractivas, como escribir una interfaz en la firma y transmitir a los demás, o agregar parámetros separados para cada interfaz y exigir que apunten al mismo objeto. Ni siquiera puede resolverlo declarando una nueva interfaz vacía que hereda de las que necesita, porque un tipo no se considerará automáticamente como la implementación de su nueva interfaz solo porque implementa sus antecesores. (Si pudiera declarar implementaciones después del hecho, esto no sería un problema, pero sí, tampoco puede hacer eso).

  • Como en el caso inverso al anterior, puede requerir que dos parámetros tengan tipos que implementen una interfaz particular y que sean del mismo tipo. Con las interfaces OOP, solo puede especificar la primera parte.

  • Las declaraciones de instancia para clases de tipo son más flexibles. Con las interfaces OOP, solo puede decir "Estoy declarando un tipo X e implementa la interfaz Y", donde X e Y son específicos. Con clases de tipos, puede decir "todos los tipos de Lista cuyos tipos de elementos satisfacen estas condiciones son miembros de Y". (También puede decir que "todos los tipos que son miembros de X e Y también son miembros de Z", aunque en Haskell esto es problemático por varias razones).

  • Las llamadas "restricciones de superclase" son más flexibles que la mera herencia de interfaz. Con las interfaces OOP, solo puede decir "para que un tipo implemente esta interfaz, también debe implementar estas otras interfaces". Ese es el caso más común también con clases de tipos, pero las restricciones de superclase también le permiten decir cosas como "SomeTypeConstructor <ThisType> debe implementar la interfaz de tal y tal", o "los resultados de esta función de tipo aplicados al tipo deben satisfacer así- y-por lo tanto restricción ", y así sucesivamente.

  • Esta es actualmente una extensión de idioma en Haskell (al igual que las funciones de tipo), pero puede declarar clases de tipos que implican varios tipos. Por ejemplo, una clase de isomorfismo: la clase de pares de tipos en los que puede convertir sin pérdidas de uno a otro y viceversa. De nuevo, no es posible con las interfaces OOP.

  • Estoy seguro de que hay más.

Vale la pena señalar que en los lenguajes de OOP que agregan genéricos, algunas de estas limitaciones se pueden borrar (cuarto, quinto, posiblemente segundos puntos).

Por otro lado, hay dos cosas importantes que pueden hacer las interfaces de OOP y las clases de tipo nativo no:

  • Despacho dinámico en tiempo de ejecución. En los lenguajes de OOP, es trivial pasar y guardar punteros a un objeto que implementa una interfaz, e invocar métodos en el tiempo de ejecución que se resolverán de acuerdo con el tipo de tiempo de ejecución dinámico del objeto. Por el contrario, las restricciones de clase de tipo se determinan por defecto en tiempo de compilación, y quizás sorprendentemente, en la gran mayoría de los casos, esto es todo lo que necesita. Si necesita despacho dinámico, puede usar lo que se llama tipos existenciales (que actualmente son una extensión de idioma en Haskell): un constructo donde ''olvida'' qué tipo de objeto era, y solo recuerda (a su opción) que obedeció ciertas restricciones de clase de tipo. Desde ese punto, se comporta básicamente de la misma manera que los punteros o las referencias a objetos que implementan interfaces en lenguajes OOP, y las clases de tipos no tienen ningún déficit en esta área. (Debe señalarse que si tiene dos existenciales que implementan la misma clase de tipo y un método de clase de tipo que requiere dos parámetros de su tipo, no puede usar los existenciales como parámetros, porque no puede saber si los existenciales tienen el mismo tipo. Pero en comparación con los lenguajes de OOP, que no pueden tener tales métodos en primer lugar, esto no es una pérdida).

  • Tiempo de ejecución de objetos a las interfaces. En los lenguajes de OOP, puede tomar un puntero o referencia en el tiempo de ejecución y probar si implementa una interfaz, y ''lanzarla'' a esa interfaz si es así. Las clases de tipo no tienen nada equivalente (lo cual es en algunos aspectos una ventaja, porque conserva una propiedad llamada ''parametricidad'', pero no entraré en eso aquí). Por supuesto, no hay nada que le impida agregar una nueva clase de tipo (o aumentar una existente) con métodos para convertir objetos del tipo a existenciales de cualquier clase de tipo que desee. (También puede implementar dicha capacidad de manera más genérica como una biblioteca, pero es mucho más complicado. Tengo la intención de terminarlo y subirlo a Hackage algún día , ¡lo prometo!)

(Debo señalar que mientras usted * puede * hacer estas cosas, muchas personas consideran que emular el OOP de esa manera es un mal estilo y sugieren que use soluciones más directas, como registros explícitos de funciones en lugar de clases de tipos. Con funciones completas de primera clase, esa opción no es menos poderosa).

Operacionalmente, las interfaces OOP se implementan generalmente almacenando un puntero o punteros en el objeto mismo que apuntan a tablas de punteros de función para las interfaces que el objeto implementa. Las clases de tipos generalmente se implementan (para los lenguajes que hacen polimorfismo por boxeo, como Haskell, en lugar de polimorfismo por multiinstanciación, como C ++) por "passing de diccionario": el compilador pasa implícitamente el puntero a la tabla de funciones (y constantes) ) como un parámetro oculto para cada función que utiliza la clase de tipo, y la función obtiene una copia sin importar cuántos objetos estén involucrados (por lo que puede hacer las cosas mencionadas en el segundo punto anterior). La implementación de tipos existenciales se parece mucho a lo que hacen los lenguajes OOP: un puntero al diccionario de clases de tipos se almacena junto con el objeto como ''evidencia'' de que el tipo ''olvidado'' es miembro de él.

Si alguna vez ha leído acerca de la propuesta de ''conceptos'' para C ++ (como se propuso originalmente para C ++ 11), básicamente se trata de clases de tipos de Haskell reinventadas para las plantillas de C ++. A veces pienso que sería bueno tener un lenguaje que simplemente tome C ++ - with-concepts, destruya la mitad de las funciones orientadas a objetos y virtuales, limpie la sintaxis y otras verrugas, y agregue tipos existenciales para cuando necesite tiempo de ejecución despacho dinámico basado en el tipo. (Actualización: Rust es básicamente esto, con muchas otras cosas buenas).


Supongo que estás hablando de las clases de tipo Haskell. No es realmente la diferencia entre las interfaces y las clases de tipos. Como su nombre lo indica, una clase de tipo es simplemente una clase de tipos con un conjunto común de funciones (y tipos asociados, si habilita la extensión TypeFamilies).

Sin embargo, el sistema de tipos de Haskell es en sí mismo más poderoso que, por ejemplo, el sistema de tipos de C #. Eso le permite escribir clases de tipos en Haskell, que no puede expresar en C #. Incluso una clase de tipo tan simple como Functor no se puede expresar en C #:

class Functor f where fmap :: (a -> b) -> f a -> f b

El problema con C # es que los genéricos no pueden ser genéricos. En otras palabras, en C # solo los tipos de tipo * pueden ser polimórficos. Haskell permite constructores de tipo polimórficos, por lo que los tipos de cualquier tipo pueden ser polimórficos.

Esta es la razón por la cual muchas de las potentes funciones genéricas en Haskell ( mapM , liftA2 , etc.) no se pueden expresar en la mayoría de los lenguajes con un sistema de tipo menos potente.