next up previous contents
Next: Rewriting as a model Up: Functional programming in Haskell Previous: Conditional polymorphism   Contents

User defined datatypes

Haskell permits user defined datatypes. We can define structured types such as trees, stacks, queues .... To do this, we define new names, called constructors, to denote the components of the new type. In general, a constructor takes arguments. The simplest type of constructor is a constant, one that takes 0 arguments.

The simplest user defined datatype is an enumerated type, such as

   data day = Sun | Mon | Tue | Wed | Thu | Fri | Sat

More interesting are recursive types, such as a binary tree where each node can hold an integer.

   data Btreeint = Nil | Node Int Btreeint Btreeint

This says that a binary tree of integers, Btreeint, is either an empty tree, Nil, or a node which holds an integer value and contains two nested trees, corresponding to the left and right children.

Can we define polymorphic datatypes, like polymorphic functions? For instance, can we define just the structure of a binary tree without fixing the type of value stored at each node?

Yes, we can, using the notion of a type variable, as before.

   data Btree a = Nil | Node a (Btree a) (Btree a)

Note here that the complete name of the new type is Btree a not just Btree.

To take another example, we can redefine the builtin type list as follows:

   data List a = Nil | Cons a (List a)

The next step is to declare that a user defined datatype belongs to a type class such as Eq or Ord. Each type class is characterized by a set of functions that each member of the class must support--analogous to an interface in Java. For instance, for a type to belong to Eq, it must support an equality check, denoted ==. The exact implementation of == is irrelevant. For instance, we might define == on trees to be isomorphism and place Btreeint in Eq as follows.

    instance Eq Btreeint where
      Nil ==  Nil                      =  True
      (Node x t1 t2) == (Node y u1 u2) = (x == y) && (t1 == u1) 
                                                  && (t2 == u2)

The definition of == for Btreeint follows the normal syntax of a Haskell function definition--in this example, by cases.

How about making Btree a an element of Eq? We could analogously write

    instance Eq (Btree a) where
      Nil ==  Nil                      =  True
      (Node x t1 t2) == (Node y u1 u2) = (x == y) && (t1 == u1) 
                                                  && (t2 == u2)

Notice however, that this uses the test x == y for elements of the unspecified type a. In Btreeint this was not a problem because the type of x and y was known to be Int, which belongs to Eq. Here, instead, we must make the membership of Btree a in Eq conditional on the properties of the underlying type a, as follows.

    instance Eq a => Eq (Btree a) where
      Nil ==  Nil                      =  True
      (Node x t1 t2) == (Node y u1 u2) = (x == y) && (t1 == u1) 
                                                  && (t2 == u2)

In other words, ``if a is in Eq, then Btree a is in Eq with == defined as ...''

We have to be a bit careful here. With the definitions we have given, the Haskell interpreter responds as follows,

  Main> (Node 5 Nil Nil) == (Node 6 Nil Nil)
  False
  Main> (Node 5 Nil Nil) == (Node 5 Nil Nil)
  True

which is as we expect, but, somewhat unexpectedly, it says

  Main> Nil == Nil
  ERROR - Unresolved overloading
  *** Type       : Eq a => Bool
  *** Expression : Nil == Nil

This is because if we just say Nil, the interpreter cannot instantiate a to a concrete type and determine whether it belongs to Eq or not. The same happens if we try to ask whether [] == [] for the empty list that is built in to Haskell. To get around this, we have to explicitly supply information about the base type of the tree or list as follows.

  Main> (Nil::Btree Int) == Nil
  True
  Main> ([]::[Float]) == []
  False

Notice that it is sufficient to specify the base type for [] on one side of the equation. Interestingly, even if we try, we cannot assert that there are two different instances of [] in an expression. For instance, if we try the following

Prelude> ([]::[Float]) == ([]::[Int])

the response is

ERROR - Type error in application
*** Expression     : [] == []
*** Term           : []
*** Type           : [Float]
*** Does not match : [Int]


next up previous contents
Next: Rewriting as a model Up: Functional programming in Haskell Previous: Conditional polymorphism   Contents
Madhavan Mukund 2004-04-29