[Haskell-beginners] Typeclasses and "inheritance"

Chaddaï Fouché chaddai.fouche at gmail.com
Thu Jul 23 16:18:12 EDT 2009


On Thu, Jul 23, 2009 at 6:34 PM, Patrick
LeBoutillier<patrick.leboutillier at gmail.com> wrote:
>    showIPAddr :: (IPAddr a h m) => a -> String
>
> but something is still missing, I get:
>
>     Could not deduce (IPAddr a b m) from the context (IPAddr a h1 m1)
>       arising from a use of `host' at IP.hs:23:23-26
>     Possible fix:
>       add (IPAddr a b m) to the context of
>         the type signature for `showIPAddr'
>     In the second argument of `(.)', namely `host'
>     In the first argument of `($)', namely `show . host'
>     In the first argument of `(++)', namely `(show . host $ a)'

It's more or less the same problem as before : there may be several
instance of IPAddr for the same a (typeclass are open, so the fact
that there is only one such instance in your module is not relevant)
and only a appears in the context of your function, so h and m aren't
sufficiently determined, it's still ambiguous (if there are several
instance for the a you passed, Haskell has no idea how to choose one,
and this choice could change the output).

There are several solutions to this problem, one was called functional
dependencies :

> class (IPHost h, IPMask m) => IPAddr a h m | a -> h m where
>         host :: a -> h
>         mask :: a -> m

That basically say that for a given a, there is only one instance :
the choice of a determine h and m. So you guarantee to Haskell you
won't write several instance with the same a (it would protest if you
tried to).

The other, recently added in GHC, is called type family, it's an open
type function (from a type to a type), that's the solution I showed
you :

> class (IPHost (Host a), IPMask (Mask a)) => IPAddr a where
>   type Host a
>   type Mask a
>   host :: a -> Host a
>   mask :: a -> Mask a

Host and Mask are type families, type functions.

In your case I believe this solution is probably more natural since
your class really has only one parameter, a, adding others to make it
compile is pretty much a hack. There are other situations where
functional dependencies are still more natural, or work better (both
solution are deemed equivalent but there are subtle implementation
differences and their semantics is often more adapted to some case,
besides type (or data) families are pretty recent, though they works
pretty well in 6.10, they're still evolving and being refined).

Your instance would look like that :

> instance IPAddr IPv4Addr IPv4Host IPv4Mask where
>   type Host IPv4Addr = IPv4Host
>   type Mask IPv4Addr = IPv4Mask
>   host (IPv4Addr h _) = h
>   mask (IPv4Addr _ m) = m

-- 
Jedaï


More information about the Beginners mailing list