2011-12-02 24 views
5

Me gustaría definir los tipos de datos jerárquicamente, como por ejemplo:tipos jerárquicos de datos

data Cat = BigCat | SmallCat 
data Animal = Cat | Dog 

Y luego escribir una función que se llevará a animales como argumentos, y escribir con coincidencia de patrones así:

bigger::Animal -> Animal -> Bool 
bigger SmallCat BigCat = False 
bigger BigCat SmallCat = True 
bigger Dog Cat = True 
bigger Cat Dog = False 

El compilador se queja. No desea coincidir con el tipo Animal explícitamente escrito en la firma de la función contra el tipo Cat en la primera y segunda líneas de coincidencia de patrones. ¿Por qué no admitirá Haskell que un gato grande o un gato pequeño es un animal?

+7

"¿Por qué Haskell no admite que un gato grande o un gato pequeño es un animal?" - Porque no lo es. 'BigCat :: Cat' y' SmallCat :: Cat', pero 'Cat :: Animal'. El * constructor * 'Cat' no tiene relación con * type *' Cat'. – delnan

Respuesta

18

Mezcla tipos con sus constructores. Un tipo es algo de lo que puedes crear variables. Un constructor de tipo es lo que usa para crear dichos datos. En su código, data Animal = Cat | Dog declara un tipo Animal con los dos constructores Cat y Dog. En la otra línea, define un tipo de datos Cat. Esto no es problema, ya que los tipos y constructores no comparten el mismo espacio de nombres.

Si usted quiere tener un objeto de tipo Cat incrustado en su Animal (si el constructor Cat se utiliza), se puede añadir un campo para el constructor:

data Animal = Cat Cat | Dog 

Esto significa: "Animal es un tipo que tiene dos constructores, Cat y Dog. Cat tiene un campo de tipo Cat y Dog no tiene ninguno ". Si desea crear objetos con el constructor Cat, tiene que pasar un objeto de tipo Cat a ella:

myCat = Cat BigCat 

Si desea hacer coincidir en una Animal, usted tiene que enumerar todos los campos de la constructora emparejado . Comparar una versión corregida de su código:

data Cat = BigCat | SmallCat 
data Animal = Cat Cat | Dog 

bigger :: Animal -> Animal -> Bool 
bigger (Cat SmallCat) (Cat BigCat) = False 
bigger (Cat BigCat) (Cat SmallCat) = True 
bigger Dog   (Cat _)  = True 
bigger (Cat _)   Dog   = False 

El _ denota un no les importa - independientemente del objeto pasado, esto siempre va a coincidir.

+5

+1 para una explicación de la distinción entre tipos y constructores apropiados para un principiante. – luqui

12

El error inmediata aquí es que Animal es la definición de dos constructores de datos, que no tienen nada que ver con Cat: La expresión es de tipo CatAnimal, mientras que la expresión es de tipo BigCatCat.

Para crear tipos de datos anidadas en forma simple, que había necesidad de hacer que el tipo Cat un argumento para el constructor relevante:

data Cat = BigCat | SmallCat 
data Animal = Cat Cat | Dog 

entonces usted puede hacer algo como esto:

bigger (Cat SmallCat) (Cat BigCat) = False 
bigger (Cat BigCat) (Cat SmallCat) = True 
bigger Dog (Cat _) = True 
bigger (Cat _) Dog = False 

Esto se vuelve excesivamente torpe si se extiende más allá de un ejemplo trivial, sin embargo, la redundancia en el tipo Cat es dolorosa, y los dos usos diferentes del identificador Cat son innecesariamente confusos. Una ligera mejora es evitar confundir el tamaño de las especies, y en lugar de hacer algo como esto:

data Size = Big | Small 
data Species = Cat | Dog 
data Animal = Animal Species Size 

Una ventaja aquí es que se puede extender más fácilmente cualquiera de los tipos sin tener que agregar tanto sin sentido repetitivo que de otro modo sería necesaria .

Sin embargo, ambos son muy tontos como cualquier otro que no sean ejemplos de juguetes y en el uso real es muy probable que haya un enfoque mucho mejor que sería preferible. Si los tipos realmente son enumeraciones simples más significativas que los gatos y los perros, entonces derivingOrd, Enum, & c. es preferible a cosas especiales. Si la intención es una forma más abierta de modelar entidades con varias propiedades, vale la pena pensar en otros diseños más adaptados al problema real.

Cuestiones relacionadas