Typeclasses and "inheritance"

Hi, I'm playing around with typeclasses and trying to a feel on how you implement "inheritance" (not sure if that's the good word here) in Haskell. I have the following code that doesn't compile: class (Show a) => IPHost a where class (Show a) => IPMask a where class IPAddr a where host :: (IPHost b) => a -> b mask :: (IPMask b) => a -> b showIPAddr :: (IPAddr a) => a -> String showIPAddr a = (show . host $ a) ++ "/" ++ (show . mask $ a) ghci says: Net/IP.hs:23:23: Ambiguous type variable `b' in the constraint: `IPHost b' arising from a use of `host' at Net/IP.hs:23:23-26 Probable fix: add a type signature that fixes these type variable(s) Net/IP.hs:23:51: Ambiguous type variable `b1' in the constraint: `IPMask b1' arising from a use of `mask' at Net/IP.hs:23:51-54 Probable fix: add a type signature that fixes these type variable(s) What exactly does this error mean in this case? It's no tclear to me what needs to be made more specific. Am I approaching the problem in the wrong way? Any help is appreciated. Thanks, Patrick -- ===================== Patrick LeBoutillier Rosemère, Québec, Canada

On Thu, Jul 23, 2009 at 10:55:59AM -0400, Patrick LeBoutillier wrote:
Hi,
I'm playing around with typeclasses and trying to a feel on how you implement "inheritance" (not sure if that's the good word here) in Haskell. I have the following code that doesn't compile:
class (Show a) => IPHost a where
class (Show a) => IPMask a where
class IPAddr a where host :: (IPHost b) => a -> b mask :: (IPMask b) => a -> b
showIPAddr :: (IPAddr a) => a -> String showIPAddr a = (show . host $ a) ++ "/" ++ (show . mask $ a)
ghci says:
Net/IP.hs:23:23: Ambiguous type variable `b' in the constraint: `IPHost b' arising from a use of `host' at Net/IP.hs:23:23-26 Probable fix: add a type signature that fixes these type variable(s)
Net/IP.hs:23:51: Ambiguous type variable `b1' in the constraint: `IPMask b1' arising from a use of `mask' at Net/IP.hs:23:51-54 Probable fix: add a type signature that fixes these type variable(s)
Think about what (show . host $ a) does. It takes 'a', converts it to... any type which is an instance of IPHost, and then shows that, turning it into a String. But there can be multiple types which are instances of IPHost, and each of them could have a *different* Show instance. So the exact String you get out depends on what type you pick... but there's no way for Haskell to infer which type to use. This is why it suggests to add a type signature. But I think there is probably some confusion that needs to be cleared up, the code you have doesn't quite make sense. Here are some questions to ask yourself: 1. Do you really want to be able to have multiple instances for IPHost and IPMask? (The answer might legitimately be 'yes', I'm just making sure.) What sorts of types do you have in mind that will be instances of these classes? 2. Do you really intend for the 'host' and 'mask' methods of the IPAddr class to be able to return *any* types which are instances of IPHost and IPMask, respectively? (This is what the code says right now.) This is actually impossible given that IPHost and IPMask have no methods. Or do you mean that for a given instance of IPAddr, the 'host' method (say) will return some *particular* type which happens to be an instance of IPHost, but which type is returned may differ between instances of IPAddr? If that's really what you mean, I would suggest either using a multi-parameter type class, like so: class (IPHost h, IPMask m) => IPAddr a h m where host :: a -> h mask :: a -> m OR using existential quantification to hide the particular types returned by 'host' and 'mask'. -Brent

Hi Brent,
Think about what (show . host $ a) does. It takes 'a', converts it to... any type which is an instance of IPHost, and then shows that, turning it into a String. But there can be multiple types which are instances of IPHost, and each of them could have a *different* Show instance. So the exact String you get out depends on what type you pick... but there's no way for Haskell to infer which type to use. This is why it suggests to add a type signature. But I think there is probably some confusion that needs to be cleared up, the code you have doesn't quite make sense. Here are some questions to ask yourself:
1. Do you really want to be able to have multiple instances for IPHost and IPMask? (The answer might legitimately be 'yes', I'm just making sure.) What sorts of types do you have in mind that will be instances of these classes?
Yes. Basically my goal was to have something like: data IPv4Host = IPv4Host Word32 deriving (Show) instance IPHost IPv4Host where data IPv4Mask = IPv4Mask Word32 deriving (Show) instance IPMask IPv4Mask where data IPv4Addr = IPv4Addr IPv4Host IPv4Mask instance IPAddr IPv4Addr where and the equivalent for IPv6 (to be implemented later when I fugre out how to do it...)
2. Do you really intend for the 'host' and 'mask' methods of the IPAddr class to be able to return *any* types which are instances of IPHost and IPMask, respectively? (This is what the code says right now.) This is actually impossible given that IPHost and IPMask have no methods. Or do you mean that for a given instance of IPAddr, the 'host' method (say) will return some *particular* type which happens to be an instance of IPHost, but which type is returned may differ between instances of IPAddr?
Yes! That's exactly what I want (and yes there will be methods eventually in those classes)
If that's really what you mean, I would suggest either using a multi-parameter type class, like so:
class (IPHost h, IPMask m) => IPAddr a h m where host :: a -> h mask :: a -> m
Ok... So I guess the instance declaration for IPv4Addr will then become this: instance IPAddr IPv4Addr IPv4Host IPv4Mask where but how do I adjust the showIPAddr function to deal with that? I tried this: 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)' Thanks a lot, Patrick
OR using existential quantification to hide the particular types returned by 'host' and 'mask'.
-Brent _______________________________________________ Beginners mailing list Beginners@haskell.org http://www.haskell.org/mailman/listinfo/beginners
-- ===================== Patrick LeBoutillier Rosemère, Québec, Canada

On Thu, Jul 23, 2009 at 6:34 PM, Patrick
LeBoutillier
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ï

On Thu, Jul 23, 2009 at 10:18 PM, Chaddaï
Fouché
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
Oops... I forgot to trim the extra parameters !
instance IPAddr IPv4Addr where type Host IPv4Addr = IPv4Host type Mask IPv4Addr = IPv4Mask host (IPv4Addr h _) = h mask (IPv4Addr _ m) = m
One advantage compared to the multiparameter + functional dependencies solution is that you can write : (IPAddr a) => ... In your context rather than introducing h and m when they're not needed. If you need to write "Host a" several time in a function, you can put the following in your context : (IPAdrr a, Host a ~ h) => ... and use h for Host a thereafter. -- Jedaï

Hi,
On Thu, Jul 23, 2009 at 4:26 PM, Chaddaï Fouché
On Thu, Jul 23, 2009 at 10:18 PM, Chaddaï Fouché
wrote: Your instance would look like that :
Oops... I forgot to trim the extra parameters !
instance IPAddr IPv4Addr where type Host IPv4Addr = IPv4Host type Mask IPv4Addr = IPv4Mask host (IPv4Addr h _) = h mask (IPv4Addr _ m) = m
This is great stuff and exactly what I wanted. Thank you very much guys! I kept on hacking at it and so far I have this code: http://hpaste.org/fastcgi/hpaste.fcgi/view?id=7428 which unfortunately doesn't compile: Net/IP.hs:44:39: Couldn't match expected type `Word (Host a)' against inferred type `Word (Mask a)' In the second argument of `($)', namely `(bits h) .&. (bits m)' In the first argument of `makeIPAddr', namely `(fromBits $ (bits h) .&. (bits m))' In the first argument of `($)', namely `makeIPAddr (fromBits $ (bits h) .&. (bits m))' In the declaration of the class IPAddr, is there any way to force that the IPHost and IPMask types are made up from the same IPBits type? Basically I would like the compiler to enforce that Word (Host a) and Word (Mask a) be the same type for a specific instance of IPAddr. Note: I'm not sure how practical all this is going to be in the end (perhaps a bit to convoluted), but it's an excellent learning exercise for me. Thanks a lot, Patrick
One advantage compared to the multiparameter + functional dependencies solution is that you can write : (IPAddr a) => ... In your context rather than introducing h and m when they're not needed.
If you need to write "Host a" several time in a function, you can put the following in your context : (IPAdrr a, Host a ~ h) => ... and use h for Host a thereafter.
-- Jedaï
-- ===================== Patrick LeBoutillier Rosemère, Québec, Canada

On Fri, Jul 24, 2009 at 6:16 PM, Patrick
LeBoutillier
In the declaration of the class IPAddr, is there any way to force that the IPHost and IPMask types are made up from the same IPBits type? Basically I would like the compiler to enforce that Word (Host a) and Word (Mask a) be the same type for a specific instance of IPAddr.
Unfortunately, this has not been implemented yet (in 6.10), though it should be in a future version of GHC (pretty soon probably), you'll then be able to write :
class (IPHost (Host a), IPMask (Mask a), Word (Host a) ~ Word (Mask a)) => IPAddr a where
but for now you must content yourself with adding it to the function context :
-- Takes an IPAddr and returns another one describing the network subnet :: (Word (Host a) ~ Word (Mask a)) => a -> a subnet a = let m = mask a h = host a in makeIPAddr (fromBits $ (bits h) .&. (bits m)) $ m
Note that I didn't put a IPAddr context since in your code subnet is a method of this class (with a default implementation), if this wasn't your intention you should correct the indentation. -- Jedaï

-- Takes an IPAddr and returns another one describing the network subnet :: (Word (Host a) ~ Word (Mask a)) => a -> a subnet a = let m = mask a h = host a in makeIPAddr (fromBits $ (bits h) .&. (bits m)) $ m
Excellent!
Note that I didn't put a IPAddr context since in your code subnet is a method of this class (with a default implementation), if this wasn't your intention you should correct the indentation.
You are right, the context was not necessary. The function is in fact a default implementation. BTW: Does what I'm trying to do make any sense at all? Does anyone know of a better/simpler way to do this (i.e making most of these computations independant of the exact underlying type)? Anyways, thanks a lot for your help, Patrick
-- Jedaï
-- ===================== Patrick LeBoutillier Rosemère, Québec, Canada

Patrick LeBoutillier wrote:
BTW: Does what I'm trying to do make any sense at all? Does anyone know of a better/simpler way to do this (i.e making most of these computations independant of the exact underlying type)?
Actually, I would try to mostly avoid typeclasses. If you want to change the representation sometime, that's still possible if you have abstraction! maybe like data IPAddr n = IPAddr n n where typically n would be Word32 or Word128(does that exist?), and a polymorphic function doing math on it might use a (Num n) => context. Actually you might sometimes need to know what kind of address it is. Then I guess we could have data IPVersion = V4 | V6 class IPAddrRepr n where ipVersion :: n -> IPVersion instance IPAddrRepr Word32 where ipVersion _ = V4 instance IPAddrRepr Word128 where ipVersion _ = V6 -- and for convenience, maybe, instance (IPAddrRepr n) => IPAddrRepr (IPAddr n) where ipVersion (IPAddr n _) = ipVersion n Alternatively I have an inkling that you could play with GADTs. But the basic Haskell type system is surprisingly powerful itself! There might be a way to write what you want without extensions. It depends on exactly what you're trying to do. For some purposes we might need to change/add to have a data IPVersioned = V4 (IPAddr Word32) | V6 (IPAddr Word128) -Isaac

On Thu, Jul 23, 2009 at 4:55 PM, Patrick
LeBoutillier
Hi,
I'm playing around with typeclasses and trying to a feel on how you implement "inheritance" (not sure if that's the good word here) in Haskell.
We don't do "inheritance" in Haskell (though you can simulate it thanks to the power of the type system you generally don't need to as there is often a more natural solution with the other features of the type system). It would be more usefull if you could describe what you want to do in general and why you think you need inheritance for it.
class (Show a) => IPHost a where
class (Show a) => IPMask a where
Those class are basically synonyms of Show, is that intentional (and really useful ?) or do you think you'll add some other functions in there ?
class IPAddr a where host :: (IPHost b) => a -> b mask :: (IPMask b) => a -> b
This definition basically say that for a given instance a of IPAddr host can return any type that is an instance of IPHost, it so happen that with what we saw from your code so far this means that you can't write instances of IPAddr (since there are no function in IPHost and a typeclass is open)... so I somehow doubt that was what you wanted to say. Probably you wanted something like :
class (IPHost (Host a), IPMask (Mask a)) => IPAddr a where type Host a type Mask a host :: a -> Host a mask :: a -> Mask a
Now each instance of IPAddr will associate two type to the instancied type, one that will be an instance of IPHost and one of IPMask. And this function will compile :
showIPAddr :: (IPAddr a) => a -> String showIPAddr a = (show . host $ a) ++ "/" ++ (show . mask $ a)
I'm equally pretty sure that's not really what you want, but without more details I can't help much more. -- Jedaï
participants (4)
-
Brent Yorgey
-
Chaddaï Fouché
-
Isaac Dupree
-
Patrick LeBoutillier