Indeed, you can give the Tree a type parameter, as in Alexander Poluektov's example. Simple enough! But why stop there? We can have a bit more fun than just that. Instead of just a recursive structure with polymorphic data, you can make the structure polymorphic in the recursion itself!
First, abstract away the tree's references to itself, in the same fashion as abstracting away the references to Int
, replacing the recursive references with a new parameter t
. That leaves us with this rather vague data structure:
data TNode t a = Empty
| Leaf a
| Node (t a) a (t a)
deriving (Eq, Ord, Show, Read)
This has been renamed as TNode
here because it isn't really a tree anymore; just a simple data type. Now, to recover the original recursion and create a tree, we twist TNode
around and feed it to itself:
newtype Tree a = Tree (TNode Tree a) deriving (Eq, Ord, Show, Read)
Now we can use this Tree
recursively, though sadly at the cost of some extra verbiage, like so:
Tree (Node (Tree Empty) 5 (Tree (Leaf 2)))
So what does this give us, besides extra typing, you ask? Simply that we've separated the fundamental tree structure from both the data it contains and the method by which the data is constructed and processed, allowing us to write more generic functions to deal with one aspect or another.
For instance, we can decorate trees with extra data, or splice extra stuff into the tree, without impacting any generic tree functions. Say we wanted to give a name to each piece of the tree:
newtype NameTree a = NameTree (String, TNode NameTree a) deriving (Eq, Ord, Show, Read)
On the other hand, we can write generic tree traversal logic:
toList f t = toList' f (f t) []
where toList' f (Node t1 x t2) xs = toList' f (f t1) (x : toList' f (f t2) xs)
toList' f (Leaf x) xs = x:xs
toList' _ Empty xs = xs
Given a function to extract the current TNode
from a recursive tree, we can use this on any such structure:
treeToList = toList (\(Tree t) -> t)
nameTreeToList = toList (\(NameTree (_, t)) -> t)
Of course, this probably goes far beyond what you're looking to do, but it's a nice taste of just how much polymorphism and generic code Haskell allows (nay, encourages) a programmer to create.