- Standard Classes. Consider the Haskell data definition for a kind of binary tree.
data Tree a = Tip a | Fork (Tree a) (Tree a)
I have included one instance declaration for the Show class below. Note
how the instance has a prerequisite Show instance for the type parameter 'a'.
instance Show a => Show (Tree a) where
show (Tip a) = "(Tip "++ show a ++ ")"
show (Fork x y) = "(Fork " ++ show x ++" "++ show y ++")"
Copy and paste these definition into your solution file (or start with the template above) and add
the declarations for the following standard instances. Hint, you will need prerequisites
for these as well. You may NOT use any deriving clauses in your solutions.
- Eq class
- Ord class. You might study the Ord class before writing your solution. It has many default definitions, so you only need write actual definitions for some of the methods. In fact there are
several choices of what set of definitions you can make.
- Functor class
- Multiparameter classes. Two types are convertible if there is a pair of
functions that convert back and forth from one type to the other. It should be an invariant
that these two functions are both left and right inverses. We can capture the structure
(but not the invariant) of this using a multiparameter type class.
class Convertible a b where
into:: a -> b
outof:: b -> a
An example of this that a pair of lists (of the same length) is convertible
with a list of pairs. We express this with the following instance.
instance Convertible ([a],[b]) [(a,b)] where
into (x,y) = zip x y
outof xs = unzip xs
Write instance declarations that respect both the structure and the invariant for the following types.
You may or may not need prerequisites instances.
-
Convertible Bool (Maybe ())
-
Convertible (a -> b -> c) ((a,b) -> c)
-
Convertible [a] (Int -> a) -- you may assume that the list is infinite, and the Int is >= 0
- A tagged expression is a rich data structure that can be used to encode a rich variety of
Haskell types.
data TagExp = I Int | C Char | F (TagExp -> TagExp) | D String [TagExp]
One may think of the constructors, I, C, F, and D as tags. The constructor tells
what kind of data is stored inside. An example encoding is the embedding of a list of Int
into a TagExp. This can be defined by the following function.
encodeL :: [Int] -> TagExp
encodeL [] = D "[]" []
encodeL (x:xs) = D ":" [I x,encodeL xs]
One can decode a list of integer using the following function.
decodeL:: TagExp -> [Int]
decodeL (D "[]" []) = []
decodeL (D ":" [I x,xs]) = x : decodeL xs
decodeL other = error "Not a Tagged list"
First note that the decodeL is a partial function. There are plenty of TagExp that
don't encode lists.
These examples suggest a rich mechanism for creating Convertible instances. Write the
following instances.
-
Convertible Int TagExp
-
Convertibale Char TagExp
-
Convertible a TagExp => Convertible [a] TagExp
-
Convertible a TagExp => Convertible (Tree a) TagExp -- this is the Tree from question 1
-
(Convertible a TagExp,Convertible b TagExp) => Convertible (a->b) TagExp
-
(Convertible a TagExp,Convertible b TagExp) => Convertible (a,b) TagExp
- Generics. Consider the following class definition.
class Convertible a TagExp => Generic a where
toString:: a -> String
equal:: a -> a -> Bool
flatten:: a -> [Int]
unflatten:: [Int] -> (a,[Int])
The basic idea is that we call any type that is Convertible to a TagExp, a Genric type.
Generic types support a wide variety operations. Here is a partial instance for pairs
instance (Generic a,Generic b) => Generic (a,b) where
flatten (a,b) = flatten a ++ flatten b
unflatten xs = ((x,y), zs)
where (x,ys) = unflatten xs
(y,zs) = unflatten ys
write the following instances for Generic. You may have to add some Convertible instances
to complete the Genric instances.
- Int
- Char
- Bool
- [a]
- Tree a
- and finish the missing methods for pairs.
There are two ways to do this. The obvious way (but this requires lots of work) is to write an instance for
every type (like we did for Generic (a,b) above). A better way is to add default
definitions for each method to the class definition. These default definitions will exploit
the operations of Convertible (into and outof). This way an instance
definition need not write any code at all!