I would like to define data types hierarchically, such as:
data Cat = BigCat | SmallCat
data Animal = Cat | Dog
And then write a function that will take Animals as arguments, and write it with pattern matching like this:
bigger::Animal -> Animal -> Bool
bigger SmallCat BigCat = False
bigger BigCat SmallCat = True
bigger Dog Cat = True
bigger Cat Dog = False
The compiler complains. It doesn't want to match the type Animal
explicitely written in the function signature against the type Cat
in the first and second lines of pattern matching. Why won't haskell admit that a big cat or small cat is an animal?
Computer Data Hierarchy: Bits, Characters, fields, records, files, database bigdata.
Tree is an example of hierarchical data structure.
A hierarchical database is a data model in which data is stored in the form of records and organized into a tree-like structure, or parent-child structure, in which one parent node can have many child nodes connected through links.
You mix up types with their constructors. A type is something of which you can create variables. A type constructor is what you use to create such data. In you code, data Animal = Cat | Dog
declares a type Animal
with the two constructors Cat
and Dog
. In the other line, you define a datatype Cat
. This is no problem since types and constructors don't share the same namespace.
If you want to have an object of type Cat
embedded in your Animal
(if the constructor Cat
is used), you can add a field to the constructor:
data Animal = Cat Cat | Dog
This means: "Animal
is a type that has two constructors, Cat
and Dog
. Cat
has a field of type Cat
and Dog
has none." If you want to create objects with the constructor Cat
, you have to pass an object of type Cat
to it:
myCat = Cat BigCat
If you want to match on an Animal
, you have to list all fields of the matched constructor. Compare a corrected version of your code:
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
The _
denotes a don't care - regardless of the object passed, this will always match.
The immediate error here is that Animal
is defining two data constructors, which have nothing to do with Cat
: The expression Cat
is of type Animal
, while the expression BigCat
is of type Cat
.
To create nested data types in simple fashion, you'd need to make the Cat
type an argument to the relevant constructor:
data Cat = BigCat | SmallCat
data Animal = Cat Cat | Dog
You can then do something like this:
bigger (Cat SmallCat) (Cat BigCat) = False
bigger (Cat BigCat) (Cat SmallCat) = True
bigger Dog (Cat _) = True
bigger (Cat _) Dog = False
This becomes exceeding clumsy if extended beyond a trivial example, however, the redundancy in the Cat
type is painful, and the two different uses of the identifier Cat
is needlessly confusing. A slight improvement is to avoid conflating size with species, and instead do something like this:
data Size = Big | Small
data Species = Cat | Dog
data Animal = Animal Species Size
An advantage here is that you can more easily extend either types without having to add as much boilerplate nonsense as would otherwise be required.
However, both of these are very silly as anything other than toy examples and in actual use there's very likely to be a much better approach that would be preferable. If the types really are simple enumerations more meaningful than cats and dogs, then deriving
Ord
, Enum
, &c. is preferable to special-purpose stuff. If the intent is a more open-ended way of modelling entities with various properties, it's worth thinking about other designs more tailored to the actual problem.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With