No creo entender las clases de tipos. Leí en alguna parte que pensar que las clases de tipos son "interfaces" (de OO) que implementa un tipo es incorrecto y engañoso. El problema es que tengo problemas para verlos como algo diferente y cómo eso está mal.
Por ejemplo, si tengo una clase de tipo (en sintaxis de Haskell)
class Functor f where
fmap :: (a -> b) -> f a -> f b
¿Cómo es eso diferente de la interfaz [1] (en la sintaxis de Java)
interface Functor<A> {
<B> Functor<B> fmap(Function<B, A> fn)
}
interface Function<Return, Argument> {
Return apply(Argument arg);
}
Una posible diferencia que se me ocurre es que la implementación de la clase de tipo utilizada en una determinada invocación no se especifica sino que se determina desde el entorno, por ejemplo, examinando los módulos disponibles para una implementación para este tipo. Parece ser un artefacto de implementación que podría abordarse en un lenguaje OO; como el compilador (o tiempo de ejecución) podría buscar un wrapper / extender / monkey-patcher que exponga la interfaz necesaria en el tipo.
¿Qué me estoy perdiendo?
[1] Tenga en cuenta que el f a
argumento se ha eliminado fmap
ya que dado que es un lenguaje OO, estaría llamando a este método en un objeto. Esta interfaz asume que el f a
argumento ha sido arreglado.
C
sin la presencia de abatidos?Además de la excelente respuesta de Andreas, tenga en cuenta que las clases de tipos están destinadas a simplificar la sobrecarga , lo que afecta el espacio de nombres global. No hay sobrecarga en Haskell que no sea lo que puede obtener a través de clases de tipo. Por el contrario, cuando utiliza interfaces de objetos, solo aquellas funciones que se declaran que toman argumentos de esa interfaz deberán preocuparse por los nombres de las funciones en esa interfaz. Entonces, las interfaces proporcionan espacios de nombres locales.
Por ejemplo, tenía
fmap
en una interfaz de objeto llamada "Functor". Estaría perfectamente bien tener otrofmap
en otra interfaz, diga "Structor". Cada objeto (o clase) puede elegir qué interfaz quiere implementar. Por el contrario, en Haskell, solo puede tener unofmap
dentro de un contexto particular. No puede importar las clases de tipo Functor y Structor en el mismo contexto.Las interfaces de objetos son más similares a las firmas ML estándar que a las clases de tipos.
fuente
En su ejemplo concreto (con la clase de tipo Functor), las implementaciones de Haskell y Java se comportan de manera diferente. Imagine que tiene el tipo de datos Quizás y quiere que sea Functor (es un tipo de datos muy popular en Haskell, que también puede implementar fácilmente en Java). En su ejemplo de Java, hará que la clase Quizás implemente su interfaz Functor. Entonces puede escribir lo siguiente (solo pseudocódigo porque solo tengo fondo de C #):
Tenga en cuenta que
res
tiene el tipo Functor, no Quizás. Esto hace que la implementación de Java sea casi inutilizable porque pierde información de tipo concreta y necesita hacer conversiones. (al menos no pude escribir tal implementación donde los tipos todavía estaban presentes). Con las clases de tipo Haskell obtendrás tal vez Int como resultado.fuente
Maybe<Int>
.