2011-12-02 21 views
5

vorrei definire i tipi di dati gerarchicamente, come ad esempio:tipi di dati gerarchici

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

E poi scrivere una funzione che avrà Animali come argomenti, e scrivere con pattern matching come questo:

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

Il compilatore si lamenta. Non vuole corrispondere al tipo Animal scritto esplicitamente nella firma della funzione rispetto al tipo Cat nella prima e nella seconda riga della corrispondenza del modello. Perché haskell non ammetterà che un gatto grande o un piccolo gatto è un animale?

+7

"Perché Haskell non ammetterà che un gatto grande o un piccolo gatto è un animale?" - Perché non lo è. 'BigCat :: Cat' e' SmallCat :: Cat', ma 'Cat :: Animal'. Il costruttore * * Cat' non ha relazione con il * tipo * 'Cat'. – delnan

risposta

18

Si confondono i tipi con i loro costruttori. Un tipo è qualcosa di cui è possibile creare variabili. Un costruttore di tipi è ciò che usi per creare tali dati. In te codice, data Animal = Cat | Dog dichiara un tipo Animal con i due costruttori Cat e Dog. Nell'altra linea, si definisce un tipo di dati Cat. Questo non è un problema poiché tipi e costruttori non condividono lo stesso spazio dei nomi.

Se si vuole avere un oggetto di tipo Cat incorporato nel tuo Animal (se il costruttore Cat è utilizzato), è possibile aggiungere un campo al costruttore:

data Animal = Cat Cat | Dog 

Ciò significa: "Animal è un tipo che ha due costruttori, Cat e . Cat ha un campo di tipo Cat e Dog non ne ha. " Se si desidera creare oggetti con il costruttore Cat, si deve passare un oggetto di tipo Cat ad esso:

myCat = Cat BigCat 

Se si desidera far corrispondere su un Animal, è necessario elencare tutti i campi del costruttore abbinato . Confronta una versione corretta del codice:

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 

Il _ denota un non importa - indipendentemente dall'oggetto passato, questo sarà sempre corrispondere.

+5

+1 per una spiegazione della distinzione tra tipi e costruttori appropriati per un principiante. – luqui

12

L'errore immediato è che Animal sta definendo due costruttori di dati, che non hanno nulla a che fare con Cat: L'espressione Cat è di tipo Animal, mentre l'espressione BigCat è di tipo Cat.

Per creare tipi di dati nidificati in modo semplice, avresti bisogno di fare il tipo di Cat un argomento al costruttore rilevanti:

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

è quindi possibile fare qualcosa del genere:

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

Questo diventa eccessivamente maldestro se esteso oltre un esempio banale, tuttavia, la ridondanza nel tipo Cat è dolorosa e i due diversi usi dell'identificatore Cat sono inutilmente confusi. Un lieve miglioramento è quello di evitare una confusione fra dimensioni, con le specie, e invece fare qualcosa del genere:

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

Un vantaggio è che è possibile estendere più facilmente entrambi i tipi senza dover aggiungere il più boilerplate una sciocchezza come sarebbe altrimenti necessario .

Tuttavia, entrambi questi sono molto sciocchi come qualcosa di diverso da esempi di giocattoli e in uso reale è molto probabile che sia un approccio molto migliore che sarebbe preferibile. Se i tipi sono in realtà semplici enumerazioni più significative di cani e gatti, quindi derivingOrd, Enum, & c. è preferibile a cose speciali. Se l'intento è un modo più aperto di modellare entità con varie proprietà, vale la pena di pensare ad altri progetti più adeguati al problema reale.

Problemi correlati