Here's a thought, engendered by a glass of beer in the Lamplighter's pub
in Gastown, Vancouver by John Hughes, Lennart Augustsson, and Simon PJ:

        type classes can model extensible records.

Suppose we want to define a record for a person:

        record Person = { Age : Int; Weight : Int }

and want to extend it to describe a Haskell programmer

        record HaskellProgrammer = Person + { LinesPerDay : Int }

We'd like any function which took a Person as an argument to
also be able to take a HaskellProgrammer.  Thus:

        isOld :: Person -> Bool
        makeSlimmer :: Person -> Person

To do this in Haskell, one could define Person and HaskellProgrammer
as classes (rather than types), thus:

        class Person a where
          setAge :: a -> Int -> a
          getAge :: a -> Int
          ...similarly for Weight...

        class Person a => HaskellProgrammer a where
          setLinesPerDay :: a -> Int -> a
          getLinesPerDay :: a -> Int

Notice that HaskellProgrammer has Person as a superclass.
Functions over records become overloaded:

        isOld :: Person a => a -> Bool
        makeSlimmer :: Person a => a -> a

That's about it really.  Of course, one might want some
syntactic sugar for record selection etc, but the point is that the
type system handles the inheritance aspects quite neatly.  You get
multiple inheritance too:

        class (Person a, Location a) => LocatedPerson a 

Here a LocatedPerson has all the fields of a Person and a Location.

There's some runtime overhead, that of passing dictionaries. That's
because representation decisions can be made independently:

        data PersonRec = PersonRec Int Int

        instance Person PersonRec where
          setAge (PersonRec age weight) new_age = PersonRec new_age weight
          ...and so on...

You can choose among various representations for HaskellProgrammer:

        data HaskellProgrammerRec = HaskellProgrammerRec Int Int Int

        instance Person HaskellProgrammerRec where
          setAge (...) new_age = ...
          ...etc...

        instance HaskellProgrammer HaskellProgrammerRec where
          setLinesPerDay (...) new_lpd = ...
          ...etc...

or

        data HaskellProgrammerRec = HaskellProgrammerRec Int PersonRec

        instance Person HaskellProgrammerRec where
          setAge (HaskellProgrammerRec lpd person) new_age 
            = HaskellProgrammerRec lpd (setAge person new_age)
          ...etc...

        ...etc...

You can even have more than one representation in use at a time.

Indeed, if we allow these "record classes" to have methods other
than simply field selection, the whole thing begins to have a rather 
object-oriented flavour.  (Method override is lacking, but we have inheritance
and a mixture of "data" and "method" fields.)

This isn't necessarily the best way to introduce records or OOP into Haskell,
but it is an example of what you can do with type classes.

Simon, John, Lennart.


Reply via email to