type classes, superclass of different kind

David Sankel camio at yahoo.com
Thu Dec 11 14:05:19 EST 2003


--- Robert Will <robertw at stud.tu-ilmenau.de> wrote:
-- > Here
-- > is a quesion for the
-- > most creative of thinkers: which is the design
(in
-- > proper Haskell or a
-- > wide-spread extension) possibly include much
-- > intermediate type classes and
-- > other stuff, that comes nearest to my desire?

Hello,

  I've often wondered the same thing.  I've found that
one can simulate several OO paradigms.  Note that
these aren't particularly elegant or simple.

----
Using Data Constructors:
----

> data Shape = Rectangle {topLeft :: (Int, Int),
bottomRight :: (Int,Int) } 
>            | Circle {center :: (Int,Int), radius ::
Int } 

This allows you have a list of shapes 

> shapeList :: [Shape]
> shapeList = [ Rectangle (-3,3) (0,0), Circle (0,0) 3
]

When you want member functions, you need to specialize
the function for
all the constructors.

> height :: Shape -> Int
> height (Rectangle (a,b) (c,d)) = b - d
> height (Circle _ radius) = 2 * radius

Disadvantages:

1) When a new Shape is needed, one needs to edit the
original Shape source 
file.
2) If a member function is not implemented for a shape
subclass, it will lead
to a run-time error (instead of compile-time).

Advantages:

1) Simple Syntax
2) Allows lists of Shapes
3) Haskell98

Example: GHC's exception types
 
http://www.haskell.org/ghc/docs/latest/html/base/Control.Exception.html

----
Using Classes
----

Classes can be used to force a type have specific
functions to act upon it.
>From our previous example:

> class Shape a where
>   height :: a -> Int
>
> data Rectangle = Rectangle {topLeft :: (Int, Int),
bottomRight :: (Int,Int) }
> data Circle = Circle {center :: (Int,Int), radius ::
Int } 
> 
> instance Shape Circle where
>   height (Circle _ radius) = 2 * radius
>
> instance Shape Rectangle where
>   height (Rectangle (a,b) (c,d)) = b - d

In this case, something is a shape if it specifically
has the member 
functions associated with Shapes (height in this
case).

Advantages
1) Simple Syntax
2) Haskell98
3) Allows a user to easily add Shapes without
modifying the original source.
4) If a member function is not implemented for a shape
subclass, it will lead
to a compile-time error.

Disadvantages:
1) Lists of Shapes not allowed

Example: Haskell 98's Num class. 
http://www.haskell.org/ghc/

----
Classes with Instance holder.
----

There have been a few proposals of ways to get around
the List of Shapes 
problem with classes.  The Haskell98 ways looks like
this

> data ShapeInstance = ShapeInstance { ci_height ::
Int }

> toShapeInstance :: (Shape a) => a -> ShapeInstance
> toShapeInstance a = ShapeInstance { ci_height =
(height a) }

> instance Shape ShapeInstance where
>   height (ShapeInstance ci_height) = ci_height

So when we want a list of shapes, we can do

> shapeList = [ toShapeInstance (Circle (3,3) 3), 
>               toShapeInstance (Rectangle (-3,3)
(0,0) ) ]

Of course this also has it's disadvantages.  Everytime
a new memeber function is added, it must be noted in
the ShapeInstance declaration, the toShapeInstance
function, and the "instance Shape ShapeInstance"
declaration.

Using a haskell extention, we can get a little better.
 Existentially quantified data constructors gives us
this:

> data ShapeInstance = forall a. Shape a =>
ShapeInstance a
> 
> instance Shape ShapeInstance where
>   height (ShapeInstance a) = height a
>
> shapeList = [ ShapeInstance (Circle (3,3) 3), 
>               ShapeInstance (Rectangle (-3,3) (0,0)
) ]

The benefits of this method are shorter code, and no
need to update the ShapeInstance declaration every
time a new member function is added.

----
Records extention
----

A different kind of inheritance can be implemented
with enhanced haskell 
records.  See
http://research.microsoft.com/~simonpj/Haskell/records.html
and
http://citeseer.nj.nec.com/gaster96polymorphic.html
for in depth explinations.  I'm not sure if these have
been impemented or not, but it would work as follows.

The inheritance provided by the above extentions is
more of a data inheritance 
than a functional inheritance. Lets say all shapes
must have a color parameter:

> type Shape = {color :: (Int,Int,Int)}
> type Circle = Shape + { center :: (Int,Int), radius
:: (Int) }
> type Rectangle = Shape + { topLeft :: (Int,Int),
bottomRight :: (Int, Int) }

So now we can reference this color for any shape by
calling .color.

> getColor :: (a <: Shape ) -> a -> (Int,Int,Int)
> getColor a = a.color

I'm not sure how the records extention could be used
with Classes with instance 
holders to provide an even more plentiful OO
environment.

So I'll conclude this email with the observation that
Haskell supports some
OO constructs although not with the most elegance.



More information about the Haskell mailing list