multiplicar - Módulos(ML) vs(Haskell) Clases de tipo
multiplicar haskell (1)
Para entender lo que dice el artículo, tómese un momento para considerar la clase de tipos Monoid en Haskell. Un monoide es cualquier tipo, T
, que tiene una función mappend :: T -> T -> T
y un elemento de identidad empty :: T
para los que se cumple lo siguiente.
a `mappend` (b `mappend` c) == (a `mappend` b) `mappend` c
a `mappend` mempty == mempty `mappend` a == a
Hay muchos tipos de Haskell que se ajustan a esta definición. Un ejemplo que viene a la mente de inmediato son los enteros, para los cuales podemos definir lo siguiente.
instance Monoid Integer where
mappend = (+)
mempty = 0
Puede confirmar que todos los requisitos se mantienen.
a + (b + c) == (a + b) + c
a + 0 == 0 + a == a
De hecho, esas condiciones se mantienen para todos los números por encima de la suma, por lo que también podemos definir lo siguiente.
instance Num a => Monoid a where
mappend = (+)
mempty = 0
Así que ahora, en GHCi, podemos hacer lo siguiente.
> mappend 3 5
8
> mempty
0
Los lectores particularmente observadores (o aquellos con experiencia en matemática) probablemente ya se habrán dado cuenta de que también podemos definir una instancia Monoid
para los números sobre la multiplicación .
instance Num a => Monoid a where
mappend = (*)
mempty = 1
a * (b * c) == (a * b) * c
a * 1 == 1 * a == a
Pero ahora el compilador encuentra un problema. ¿Qué definición de mappend
debería usar para los números? ¿ mappend 3 5
igual a 8
o 15
? No hay forma de que decida. Es por esto que Haskell no permite múltiples instancias de una sola clase de tipos. Sin embargo, el problema sigue en pie. ¿Qué instancia Monoid
de Num
deberíamos usar? Ambos son perfectamente válidos y tienen sentido para ciertas circunstancias. La solución es no usar ninguno. Si observas Monoid en Hackage, verás que no hay una instancia de Monoid
de Num
, o Integer
, Int
, Float
o Double
. En su lugar, hay instancias Monoid
de Sum
y Product
. Sum
y el Product
se definen como sigue.
newtype Sum a = Sum { getSum :: a }
newtype Product a = Product { getProduct :: a }
instance Num a => Monoid (Sum a) where
mappend (Sum a) (Sum b) = Sum $ a + b
mempty = Sum 0
instance Num a => Monoid (Product a) where
mappend (Product a) (Product b) = Product $ a * b
mempty = Product 1
Ahora, si desea usar un número como Monoid
, debe envolverlo en Sum
o Tipo de Product
. El tipo de Monoid
que utilice determinará qué instancia de Monoid
se utiliza. Esta es la esencia de lo que el artículo trataba de describir. No hay ningún sistema integrado en el sistema de clases de tipos de Haskell que le permita elegir entre varias intenciones. En su lugar, tienes que saltar a través de aros envolviéndolos y desenvolviéndolos en tipos de esqueleto. Ahora, si considera o no que esto es un problema, es una gran parte de lo que determina si prefiere Haskell o ML.
ML soluciona esto permitiendo que se definan múltiples "instancias" de la misma clase y tipo en diferentes módulos. Luego, el módulo que importe determinará qué "instancia" usará. (Estrictamente hablando, ML no tiene clases e instancias, pero sí tiene firmas y estructuras, que pueden actuar casi igual. Para una comparación más profunda, lea este documento ).
Según Harper ( https://existentialtype.wordpress.com/2011/04/16/modules-matter-most/ ), parece que las Clases de Tipo simplemente no ofrecen el mismo nivel de abstracción que ofrecen los Módulos y estoy teniendo un momento difícil averiguar exactamente por qué. Y no hay ejemplos en ese enlace, así que es difícil para mí ver las diferencias clave. También hay otros artículos sobre cómo traducir entre módulos y clases de tipo ( http://www.cse.unsw.edu.au/~chak/papers/modules-classes.pdf ), pero esto realmente no tiene nada que ver con ver con la implementación en la perspectiva del programador (solo dice que no hay algo que uno pueda hacer que el otro no pueda emular).
En concreto, en el https://existentialtype.wordpress.com/2011/04/16/modules-matter-most/ :
La primera es que insisten en que un tipo puede implementar una clase de tipo exactamente de una manera. Por ejemplo, de acuerdo con la filosofía de las clases de tipos, los enteros se pueden ordenar de una manera precisa (el ordenamiento habitual), pero obviamente hay muchos ordenamientos de interés (por ejemplo, por divisibilidad). La segunda es que confunden dos cuestiones separadas: especificar cómo un tipo implementa una clase de tipo y especificar cuándo se debe usar dicha especificación durante la inferencia de tipo.
Yo tampoco entiendo ¿Un tipo puede implementar una clase de tipos en más de 1 forma en ML? ¿Cómo tendría los enteros ordenados por divisibilidad por ejemplo sin crear un nuevo tipo? En Haskell, tendría que hacer algo como usar datos y tener la instance Ord
para ofrecer un pedido alternativo.
Y el segundo, ¿no son los dos distintos en Haskell? Especificar "cuándo se debe usar una especificación de este tipo durante la inferencia de tipo" se puede hacer mediante algo como esto:
blah :: BlahType b => ...
donde BlahType es la clase que se utiliza durante la inferencia de tipo y NO la clase de implementación. Mientras que, "cómo un tipo implementa una clase de tipo" se hace usando la instance
.
¿Alguien puede explicar lo que realmente intenta decir el enlace? Simplemente no entiendo por qué los módulos serían menos restrictivos que las clases de tipo.