Inheritance without OOHaskell

Hello,
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.
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.
2. As a library designer, what is the most friendly way around these
problems that adheres to the principle of least surprise for Haskell
programmers?
3. How does one choose between a type class and a data record of
functions when both would meet the general needs?
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?
Thanks,
-- John
--
John Goerzen

On 13/01/06, John Goerzen
Hello,
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 interface.
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, like: 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 programmers?
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 are poor. 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.) - Cale

Hello Cale,
Saturday, January 14, 2006, 12:22:08 AM, you wrote:
CG> On 13/01/06, John Goerzen

Hi, I would like to add some minor improvement on usability of the existential type Connection proposed by Bulat.
data Connection = forall c . (IsConnection c) => Conn c The thing is that you usually have functions like send :: (isConnection c) => c -> String -> IO() or whatever. In order to be able to feed a variable of type Connection into functions like send it is handy to define
instance IsConnection Connection where foo (Conn c) = foo c setFoo bar (Conn c) = Conn $ setFoo bar c ... which delegates functions "into" the item inside the exitential wrapper. Cheers, Georg -- ---- Georg Martius, Tel: (+49 34297) 89434 ---- ------- http://www.flexman.homeip.net ---------
participants (4)
-
Bulat Ziganshin
-
Cale Gibbard
-
Georg Martius
-
John Goerzen