[Haskell-cafe] Inheritance without OOHaskell
cgibbard at gmail.com
Fri Jan 13 16:22:08 EST 2006
On 13/01/06, John Goerzen <jgoerzen at complete.org> wrote:
> I'd like to put forth two situations in which I miss the ability to use
> inheritance in Haskell, and then see if maybe somebody has some insight
> that I'm missing out on.
> Situation #1: In HDBC, there is a Connection type that is more or less
> equivolent to a class on a OOP language. In Haskell, we use a record
> with named fields that represent functions. Closures are used to permit
> those functions to access internal state. That works well enough. But
> there is no way at all to extend this. Say a database such as
> PostgreSQL has some extra features -- it would be nice for the
> PostgreSQL objects to support additional functions, whereas Connection
> objects from other databases might not support those functions. Any
> Haskell function could expect a Connection object (in which case it
> could access only the standard functions) or a PostgreSQL object (in
> which case it could access the standard plus the enhanced functions).
> The internal state of a Connection object is DB-specific, so there can
> be no general function to expose it.
You're describing what sounds like a good application of type classes.
You have an interface which all connection types should support, and
specific types which may have extra properties apart from that
> Situation #2: In Python, every exception is an object, and every object
> can be extended. Therefore, I could write an exception handler for,
> say, an IO error, and have it work for anything that's a subclass of the
> generic IO error -- even if these subclasses weren't known at the time
> the program was written.
> Other handlers could be as specific or as general as desired.
> In Haskell, it seems that all of this has to be anticipated in advance;
> it's not very easy to extend things.
> So my questions are:
> 1. I have sometimes used typeclasses instead of data records. This
> provides some of what I'm searching for, but has the unfortunate
> side-effect that one can't very easily build a list of objects that may
> not come from the same place but are nonetheless part of the class. For
> instance, had I used typeclasses for HDBC, I couldn't have a list of
> Connection objects where some are from MySQL, some from PostgreSQL, etc.
If you need to hold a bunch of connections of varying types together
in a data structure at some point, you can use an existential type,
data Connection where
Conn :: (IsConnection c) => c -> Connection
In general, existential types are the solution to this problem. You
can often get around the use of existential types, but they're pretty
convenient when you run into this problem and don't want to redesign
everything. Also, without a lot of thinking, this can often lead to
just passing dictionaries around to simulate the existential type.
(otoh, it's Haskell 98, whereas existential types aren't)
> 2. As a library designer, what is the most friendly way around these
> problems that adheres to the principle of least surprise for Haskell
Existential types aren't bad. They do however indicate an OO-design
going on. Without really considering the application at hand, it can
be hard to decide how to write it in a more functional way, but the
basic concept is that data are hard to extend, so you should make them
fairly universal, and all the extending should happen by writing
additional functions (and potentially some new datatypes altogether).
Note that this is the opposite of the usual case with OO, where it's
often easy to extend the data being carried around, but it can be
quite a lot of work to extend the functional interface.
> 3. How does one choose between a type class and a data record of
> functions when both would meet the general needs?
The biggest differences between those are that
1) There can be only one typeclass instance for a given assignment of
type parameters, so if you expect that users will often want to use
the same type with the given interface in multiple ways, typeclasses
2) In almost every other situation, typeclasses are nicer, since you
don't have to pass instances around or name which one you're using
explicitly. Further, if you add a method to a typeclass, you need to
implement it in all the instances, but if you add an additional
function to your record type, you not only have to update the
'instance' records, but also likely a number of the places where
they've been used.
> 4. Is there a way to solve these inheritance problems without resorting
> to a library such as OOHaskell, which many Haskell programmers are not
> familiar with?
Well, hmm... You can try to avoid the concept of inheritance
altogether. You can give names to separate parts of interfaces via
typeclasses, and even enforce that something implementing one of these
classes implements the other.
Often, you can also take care of things via parametric polymorphism,
where the data types are parametrised over the later extensions, and
anything which doesn't use these additional data just becomes
polymorphic automatically. (So you might have a type Connection ()
which is just a basic connection, and a Connection PostgresExts, for a
connection with additional data needed for postgres. Functions which
work with any kind of connection just have something like (Connection
a) in their types.)
More information about the Haskell-Cafe