Inheritance without OOHaskell

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
4 messages Options
Reply | Threaded
Open this post in threaded view
|

Inheritance without OOHaskell

John Goerzen-3
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 <[hidden email]>    GPG: 0x8A1D9A1F    www.complete.org
"Value your freedom, or you will lose it, teaches history.  `Don't bother us
with politics,' respond those who don't want to learn."

_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: Inheritance without OOHaskell

Cale Gibbard
On 13/01/06, John Goerzen <[hidden email]> wrote:

> 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
_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re[2]: Inheritance without OOHaskell

Bulat Ziganshin
Hello Cale,

Saturday, January 14, 2006, 12:22:08 AM, you wrote:

CG> On 13/01/06, John Goerzen <[hidden email]> wrote:

i want to answer, but Gale covered all that i have to say :)  the only
additions is that i use more old-fashioned Hugs-compatible declarations
instead of GADT-style

CG> data Connection where
CG>     Conn :: (IsConnection c) => c -> Connection

data Connection = forall c . (IsConnection c) => Conn c


and that i use the "delegation" pattern:

class (Show h) => Stream h where
    vClose :: h -> IO ()
    ......
    vSetEncoding :: h -> Encoding -> IO ()
    vGetEncoding :: h -> IO Encoding
   

data WithEncoding h = WithEncoding h (IORef Encoding)

openWithEncoding encoding h = do
    e <- newIORef encoding
    return (WithEncoding h e)

instance (Stream h) => Stream (WithEncoding h) where
    vClose        (WithEncoding h _) = vClose     h
    vIsEOF        (WithEncoding h _) = vIsEOF     h
    vMkIOError    (WithEncoding h _) = vMkIOError h
    vReady        (WithEncoding h _) = vReady     h
    ......
    vSetEncoding  (WithEncoding h e) = writeIORef e
    vGetEncoding  (WithEncoding h e) = readIORef  e
   
you can see WithEncoding type as a one adding server-specific features
to general driver implementation. of course, this idea of "stream
transformers" is more general that you really requested, but the
"delegation" pattern will remain the same


--
Best regards,
 Bulat                            mailto:[hidden email]



_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: Inheritance without OOHaskell

Georg Martius
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 ---------

_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe

attachment0 (196 bytes) Download Attachment