2010-01-31 17 views
12

En primer lugar, esta pregunta no es 100% específica de Haskell, no dude en comentar sobre el diseño general de tipos de clases, interfaces y tipos.¿Por qué Haskell no llega a inferir las clases de tipos de datos en las firmas de funciones?

estoy leyendo LYAH - creating types and typeclasses El siguiente es el paso que estoy en busca de más información en:

Data (Ord k) => Map k v = ... 

Sin embargo, es una convención muy fuerte en Haskell nunca añadir clase de tipos limitaciones en declaraciones de datos. ¿Por qué? Bueno, porque no nos beneficiamos mucho, pero terminamos escribiendo más restricciones de clase , incluso cuando no las necesitamos . Si ponemos o no ponemos la restricción Ord k en la declaración de datos para Map k v, vamos a tener que poner la restricción en funciones que asumen que las claves en un mapa pueden ser ordenadas. Pero si no ponemos la restricción en la declaración de datos, no tenemos que poner (Ord k) => en el tipo declaraciones de funciones que no importa si las claves pueden ser ordenadas o no. Un ejemplo de tal función es toList, que solo toma una asignación y la convierte en una lista asociativa . Su tipo de firma es toList :: Map k a -> [(k, a)]. Si Map kv tenía una restricción de tipo en su declaración de datos , el tipo para toList debería ser toList :: (Ord k) => Map ka -> [(k, a)], aunque la función no hace ninguna comparación de las claves por orden.

Esto, al principio, parece lógico, pero ¿no hay una ventaja al tener la clase de tipo adjunta al tipo? Si la clase de tipo es el comportamiento del tipo, ¿por qué debería definirse el comportamiento mediante el uso del tipo (a través de funciones) y no el tipo en sí? Supongo que hay alguna meta-programación que podría hacer uso de ella, y sin duda es una documentación de código descriptiva y agradable. Por el contrario, ¿sería esta una buena idea en otros idiomas? ¿Sería ideal especificar la interfaz a la que el objeto debería ajustarse en el método, de modo que si el que llama no utiliza el método, el objeto no tiene que ajustarse a la interfaz? Además, ¿por qué Haskell no puede inferir que una función que utiliza el tipo Foo, tiene que obtener las restricciones de clase de tipo identificadas en la declaración del tipo Foo? ¿Hay un pragma para habilitar esto?

La primera vez que lo leí, evocó un "eso es una respuesta de pirateo (o solución alternativa)". En la segunda lectura con un pensamiento, sonó inteligente. En la tercera lectura, dibujando una comparación con el mundo de OO, sonaba como un hack nuevamente.

Así que aquí estoy.

Respuesta

4

La razón principal para evitar las restricciones de tipo de letra en las declaraciones de datos es que no logran absolutamente nada; de hecho, creo que GHC se refiere a un contexto de clase como el "contexto estúpido". La razón de esto es que el diccionario de clases no se transmite con los valores del tipo de datos, por lo que debe agregarlo a todas las funciones que operan en los valores de todos modos.

Como una forma de "forzar" la restricción de clase de tipo en las funciones que operan en el tipo de datos, tampoco logra nada; las funciones generalmente deberían ser lo más polimórficas posibles, entonces, ¿por qué obligar a la restricción a cosas que no la necesitan?

En este punto, puede pensar que debería ser posible cambiar la semántica de los ADT para llevar el diccionario con los valores. De hecho, parece que este es el objetivo de GADT s; por ejemplo, se puede hacer:

data Foo a where { Foo :: (Eq a) => a -> Foo a } 
eqfoo :: Foo t -> Foo t -> Bool 
eqfoo (Foo a) (Foo b) = a == b 

en cuenta que el tipo de eqfoo no necesita la restricción de la ecuación, ya que se "lleva" por el mismo tipo de datos Foo.

+0

Soy bastante nuevo en Haskell, una breve explicación de lo que es una GADT habría sido útil (o enlace). –

+0

He vuelto a trabajar esa sección un poco, y he agregado un enlace a la documentación de GHC para GADT. Si alguien más quiere analizar las explicaciones de lo que son GADTs, ¡sería bienvenido! – mithrandi

9

Tal vez Map k v no fue el mejor ejemplo para ilustrar el punto. Dada la definición de Map, aunque hay algunas funciones que no necesitarán la restricción (Ord k), no hay forma posible de construir un Map sin ella.

A menudo se encuentra que un tipo es bastante útil con el subconjunto de funciones que funcionan sin alguna restricción particular, incluso cuando visualizó la restricción como un aspecto obvio de su diseño original. En tales casos, al haber dejado la restricción fuera de la declaración de tipo, la deja más flexible.

Por ejemplo, Data.List contiene muchas funciones que requieren (Eq a), pero por supuesto las listas son perfectamente útiles sin esa restricción.

+1

"no hay forma posible de construir un mapa sin él". - Esta es precisamente la razón por la que siempre he pensado que cosas como los contextos de Ord en las funciones que consumen mapas serían redundantes, y también la razón por la que GADT coincide con la introducción de contextos. El hecho de que tengo un 'Map k v' ya es prueba suficiente de que k está ordenado. Si todo lo que hago es extraer cosas del mapa, por ejemplo, ¿por qué debería necesitar una instancia de Ord? – mokus

7

La respuesta corta es: Haskell hace esto porque así es como se escribe la especificación del idioma.

La respuesta larga implica citando el GHC documentation language extensions section:

Cualquier tipo de datos que pueden ser declarado en la sintaxis estándar Haskell-98 también puede ser declarada usando la sintaxis de estilo GADT. La elección es en gran parte estilística, pero las declaraciones de estilo GADT difieren en un aspecto importante: tratan las restricciones de clase en los constructores de datos de manera diferente. Específicamente, si al constructor se le da un contexto de clase de tipo, ese contexto queda disponible mediante la coincidencia de patrones. Por ejemplo:

data Set a where 
    MkSet :: Eq a => [a] -> Set a 

(...)

Todo esto contrasta con el comportamiento peculiar tratamiento de Haskell 98 de contextos en una declaración de tipo de datos (Section 4.2.1 of the Haskell 98 Report). En Haskell 98 la definición

data Eq a => Set' a = MkSet' [a] 

da MKSet' del mismo tipo que MKSet anteriormente. Pero en lugar de poner a disposición una restricción (Eq a), la coincidencia de patrones en MkSet 'requiere una restricción (Eq a). GHC implementa fielmente este comportamiento, por extraño que sea. Pero para las declaraciones de estilo GADT, el comportamiento de GHC es mucho más útil, así como mucho más intuitivo.

1

me gustaría señalar que si usted está preocupado de que se podría construir un objeto que requiere restricciones para sus operaciones, pero no lo hace para su creación, mkFoo decir, siempre se puede poner artificialmente la restricción en el mkFoo función para imponer el uso de la clase de letra por personas que usan el código. La idea también se extiende a las funciones de tipo no mkFoo que operan en Foo. Luego, al definir el módulo, no exporte nada que no haga cumplir las restricciones.

Aunque tengo que admitirlo, no veo ningún uso para esto.

Cuestiones relacionadas