Functional Dependencies conflicts

Hello, I'm trying to make two simple classe which would help me to transform unserializable datatypes to serializable ones. The classes are: class (Binary b) => Binarizable a b | a -> b where toBinary :: a -> b class (Binarizable a b, Monad m) => Unbinarizable a b m | a -> b where fromBinary :: b -> m a The idea is simple: if we have a type 'a' which cannot be serialized (for instance because it contains functions), we may turn it into a 'b' type which is instance of Binary, with of course a loss of information. And then, Unbinarizable enables us to get the original 'a' type back. fromBinary has to run inside a monad so that it can somehow recover the lost information. Now, for instance, in a simple role playing game, we would have the datatype : data GameObject = GameObject { objIdentifier :: String, objEffect :: Character -> Character } It can't obviously be declared instance of Binary, since the field objEffect is a function. So -- since an objIdentifier must be unique -- we can declare it instance of Binarizable/Unbinarizable: instance Binarizable GameObject String where toBinary = objIdentifier instance (MonadReader [GameObject] m) => Unbinarizable GameObject String m where fromBinary name = liftM getIt ask where getIt = maybe err id . find ((== name) . objIdentifier) err = error $ "Unbinarize: The object '" ++ name ++ "' doesn't exist!" To be unbinarized, we need to have a ReaderMonad which grants us access to the list of all the objects, so that we may find the object from its identifier. Well, here comes the trouble: GameStructs.hs:16:9: Functional dependencies conflict between instance declarations: instance (Binary a) => Binarizable a a -- Defined at MagBots/GameStructs.hs:16:9-37 instance Binarizable GameObject String -- Defined at MagBots/GameStructs.hs:38:9-37 GameStructs.hs:19:9: Functional dependencies conflict between instance declarations: instance (Binary a, Monad m) => Unbinarizable a a m -- Defined at MagBots/GameStructs.hs:19:9-50 instance (MonadReader [GameObject] m) => Unbinarizable GameObject String m -- Defined at MagBots/GameStructs.hs:41:9-73 I don't see why the functional dependencies conflict, since GameObject is not an instance of Binary...

Am Samstag 17 April 2010 19:14:02 schrieb Limestraël:
Hello,
Well, here comes the trouble: GameStructs.hs:16:9: Functional dependencies conflict between instance declarations: instance (Binary a) => Binarizable a a -- Defined at MagBots/GameStructs.hs:16:9-37 instance Binarizable GameObject String -- Defined at MagBots/GameStructs.hs:38:9-37
GameStructs.hs:19:9: Functional dependencies conflict between instance declarations: instance (Binary a, Monad m) => Unbinarizable a a m -- Defined at MagBots/GameStructs.hs:19:9-50 instance (MonadReader [GameObject] m) => Unbinarizable GameObject String m -- Defined at MagBots/GameStructs.hs:41:9-73
I don't see why the functional dependencies conflict, since GameObject is not an instance of Binary...
Somebody somewhere might write such an instance. But more fundamentally: GHC doesn't look at the constraints for instance selection, so your instance in line 16 looks like instance Binarizable a a where ..., oh, and by the way, a must be an instance of Binary, otherwise please refuse to compile to the compiler. The FunDep then says in each instance (and you'd need at least OverlappingInstances to declare more) a and b are the same type. instance Binarizable GameObject String violates that FunDep. (Analogous for Unbinarizable.) I think removing the instance Binary a => ... and declaring an instance for the types you need manually is the easiest solution.

Yes! Sorry, I forgot a bit:
Binary types are automatically made instances of Binarizable/Unbinarizable
(that's my line 16):
instance (Binary a) => Binarizable a a where
toBinary = id
instance (Binary a, Monad m) => Unbinarizable a a m where
fromBinary = return
To me, the functional dependency in:
class (Binary b) => Binarizable a b | a -> b
meant that for each a, there only one type b that can match.
That's what I want: for every Binary type 'a', the matching Binary is also
'a'
And for GameObject, the sole matching type is String.
In other words, GameObject implies String.
I would have undestood the error if GameObject was also an instance of
Binary (then the two instances would match), but it's not the case...
Is my FunDep wrong?
I done this especially because I didn't wanted to declare each type one by
one instance of Binarizable,
Haskell type system normally enables me to automatically define a Binary as
an instance of Binarizable.
2010/4/17 Daniel Fischer
Am Samstag 17 April 2010 19:14:02 schrieb Limestraël:
Hello,
Well, here comes the trouble: GameStructs.hs:16:9: Functional dependencies conflict between instance declarations: instance (Binary a) => Binarizable a a -- Defined at MagBots/GameStructs.hs:16:9-37 instance Binarizable GameObject String -- Defined at MagBots/GameStructs.hs:38:9-37
GameStructs.hs:19:9: Functional dependencies conflict between instance declarations: instance (Binary a, Monad m) => Unbinarizable a a m -- Defined at MagBots/GameStructs.hs:19:9-50 instance (MonadReader [GameObject] m) => Unbinarizable GameObject String m -- Defined at MagBots/GameStructs.hs:41:9-73
I don't see why the functional dependencies conflict, since GameObject is not an instance of Binary...
Somebody somewhere might write such an instance. But more fundamentally:
GHC doesn't look at the constraints for instance selection, so your instance in line 16 looks like
instance Binarizable a a where ..., oh, and by the way, a must be an instance of Binary, otherwise please refuse to compile
to the compiler. The FunDep then says in each instance (and you'd need at least OverlappingInstances to declare more) a and b are the same type. instance Binarizable GameObject String violates that FunDep. (Analogous for Unbinarizable.)
I think removing the
instance Binary a => ...
and declaring an instance for the types you need manually is the easiest solution.

Am Samstag 17 April 2010 22:01:23 schrieb Limestraël:
Yes! Sorry, I forgot a bit: Binary types are automatically made instances of Binarizable/Unbinarizable (that's my line 16):
instance (Binary a) => Binarizable a a where toBinary = id
instance (Binary a, Monad m) => Unbinarizable a a m where fromBinary = return
And that is your problem. The compiler only looks at the context (Binary a) *after* it has chosen an instance. When somewhere in the code it encounters "toBinary x", it looks for an instance declaration "instance Binarizable a b where" which matches x's type. Since you have "instance Binarizable a a", you have a matching instance and that is selected. *Now* the compiler looks at the context and barfs if x's type is not an instance of Binary.
To me, the functional dependency in: class (Binary b) => Binarizable a b | a -> b meant that for each a, there only one type b that can match.
Yes, that's what the functional dependency says.
That's what I want: for every Binary type 'a', the matching Binary is also 'a'
And that instance says "for every type 'a', the matching type is also 'a', and furthermore, 'a' is an instance of Binary". Contexts on a class and functional dependencies don't work as one would naively expect.
And for GameObject, the sole matching type is String. In other words, GameObject implies String. I would have undestood the error if GameObject was also an instance of Binary (then the two instances would match), but it's not the case...
The context isn't considered until after matching.
Is my FunDep wrong?
At least, the FunDep plus the generic instance is not what you want. Probably, what you want can be done with some type wizardry, but I don't know how. Perhaps the following will work: {-# LANGUAGE OverlappingInstances, TypeFamilies, MultiParamTypeClasses #-} class Binarizable a where type ToBin a toBinary :: a -> ToBin a class (Monad m) => Unbinarizable a m where type FromBin a fromBinary :: FromBin a -> m a instance Binarizable GameObject where type ToBin GameObject = String toBinary g = ... instance (Binary a) => Binarizable a where type ToBin a = a toBinary x = x instance (MonadReader [GameObject] m) => Unbinarizable GameObject m where type FromBin GameObject = String fromBinary s = ... instance (Monad m, Binary a) => Unbinarizable a m where type FromBin a = a fromBinary x = return x With OverlappingInstances, the most specific match is chosen, so for GameObjects, the special instance is selected.
I done this especially because I didn't wanted to declare each type one by one instance of Binarizable, Haskell type system normally enables me to automatically define a Binary as an instance of Binarizable.

On Sat, Apr 17, 2010 at 9:50 PM, Daniel Fischer
{-# LANGUAGE OverlappingInstances, [...]
but with caution: <quicksilver> using OverlappingInstances is the haskell equivalent of buying a new car with high safety rating and replacing the air bags with poison gas, pouring lubricating oil all over the brake pads, cutting the cable to the parking brake, and gluing broken glass shards all over the steering wheel. :)

Am Sonntag 18 April 2010 01:23:07 schrieb Ben Millwood:
On Sat, Apr 17, 2010 at 9:50 PM, Daniel Fischer
wrote: {-# LANGUAGE OverlappingInstances, [...]
but with caution:
<quicksilver> using OverlappingInstances is the haskell equivalent of buying a new car with high safety rating and replacing the air bags with poison gas, pouring lubricating oil all over the brake pads, cutting the cable to the parking brake, and gluing broken glass shards all over the steering wheel.
:)
Wow. Makes me wonder what quicksilver says about IncoherentInstances.

On Sun, Apr 18, 2010 at 12:45 AM, Daniel Fischer
Am Sonntag 18 April 2010 01:23:07 schrieb Ben Millwood:
On Sat, Apr 17, 2010 at 9:50 PM, Daniel Fischer
wrote: {-# LANGUAGE OverlappingInstances, [...]
but with caution:
<quicksilver> using OverlappingInstances is the haskell equivalent of buying a new car with high safety rating and replacing the air bags with poison gas, pouring lubricating oil all over the brake pads, cutting the cable to the parking brake, and gluing broken glass shards all over the steering wheel.
:)
Wow. Makes me wonder what quicksilver says about IncoherentInstances.
Actually, I found that quote while grepping my logs for this one: <@quicksilver> undecidable just turns of the termination checker, which can be find if you're doing something clever outside of the heuristics of the GHC termination checker. <@quicksilver> overlapping actually shatters the language into tiny inconsistent pieces <@quicksilver> and incoherent files off the edges of the pieces so they don't even fit together any more. and now I should probably stop using the words of other people possibly without their knowledge :P

Daniel Fischer
Wow. Makes me wonder what quicksilver says about IncoherentInstances.
Not quicksilver, but according to lambdabot: <ivanm> @quote incoherent <lambdabot> sproingie says: * enables IncoherentInstances and ends up with Sarah Palin in his living room -- Ivan Lazar Miljenovic Ivan.Miljenovic@gmail.com IvanMiljenovic.wordpress.com

There must be some kind of a private joke I don't get...
BTW, all you've said is pretty scaring...
It's strange I can't declare a generic instance for Binary types... I
thought I was trying to do something quite common in Haskell.
Apparently I'm still a young padawan with many things to learn.
Anyway, it's not the first time I get worked up with multi-param typeclasses
and functionnal dependencies....
2010/4/18 Ivan Lazar Miljenovic
Daniel Fischer
writes: Wow. Makes me wonder what quicksilver says about IncoherentInstances.
Not quicksilver, but according to lambdabot:
<ivanm> @quote incoherent <lambdabot> sproingie says: * enables IncoherentInstances and ends up with Sarah Palin in his living room
-- Ivan Lazar Miljenovic Ivan.Miljenovic@gmail.com IvanMiljenovic.wordpress.com _______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

On Apr 18, 2010, at 11:01 AM, Limestraël wrote:
It's strange I can't declare a generic instance for Binary types... I thought I was trying to do something quite common in Haskell.
A common workaround is to define a newtype like this newtype GenericBinary a = GB { fromGB :: a } and an instance like this instance Binary a => Binarizable (GenericBinary a) a where toBinary = fromGB which only needs FlexibleInstances enabled. You can then 'tag' Binary types for which you want to use the generic default instance above with the GB newtype constructor. Whether this is less of a pain than implementing a Binarizable instance for each Binary type is a different question.. Sebastian -- Underestimating the novelty of the future is a time-honored tradition. (D.G.)

It seems like a reasonable and not-too-painful solution, thanks!
Concerning Haskell typesystem, I know it to be beautiful, but also kind of
complex. One of the great Haskell assets is genericity, but this complexity
sometimes encumbers this genericity.
But still, Haskell is -- in terms of flexibility -- way before other
languages which pretend to be generic.
2010/4/18 Sebastian Fischer
On Apr 18, 2010, at 11:01 AM, Limestraël wrote:
It's strange I can't declare a generic instance for Binary types... I
thought I was trying to do something quite common in Haskell.
A common workaround is to define a newtype like this
newtype GenericBinary a = GB { fromGB :: a }
and an instance like this
instance Binary a => Binarizable (GenericBinary a) a where toBinary = fromGB
which only needs FlexibleInstances enabled.
You can then 'tag' Binary types for which you want to use the generic default instance above with the GB newtype constructor. Whether this is less of a pain than implementing a Binarizable instance for each Binary type is a different question..
Sebastian
-- Underestimating the novelty of the future is a time-honored tradition. (D.G.)

On Sun, Apr 18, 2010 at 5:01 AM, Limestraël
There must be some kind of a private joke I don't get...
BTW, all you've said is pretty scaring...
And somewhat exaggerated, of course. Reasonable uses exist for all three extensions, but they're firmly in the category of "avoid unless you know what you're doing". Well, at least two of them are, I'm not sure when IncoherentInstances is a good idea (if ever). It's worth experimenting with them in some toy code for a while before trying to use them "for real". In any case, if you do use those extensions, they can usually be isolated to some extent. A library can use them internally without requiring client code to enable them, and in an application use can be restricted to just a few modules, enabling the extensions on a per-module basis. My rule of thumb is the "sausage principle"--outside code should be able to act as if GHC somewhere picked up a more expressive means of specifying instance heads and/or a smarter termination checker and carry on blissfully ignorant of by what providence the instances were obtained. That is, if one eats sausage, it is best to not dwell on how it is made, so to speak.
It's strange I can't declare a generic instance for Binary types... I thought I was trying to do something quite common in Haskell. Apparently I'm still a young padawan with many things to learn. Anyway, it's not the first time I get worked up with multi-param typeclasses and functionnal dependencies....
What you're trying to do is perfectly reasonable, unfortunately it doesn't mesh well with how type classes/instances work. A lot of the reason why the distressing extensions under discussion exist at all is working around those limitations. Type families are a start on cleaning up one aspect of the type class system--namely, the awkwardness of functional dependencies. Unfortunately, type families don't really help on the "how to write generic but not completely general instances" right now, and in fact are incompatible with overlapping instances, making some things impossible! I think there have been some discussions of proposals toward fixing this as well, but I'm not sure what the status of those are. - C.

On Sat, Apr 17, 2010 at 4:01 PM, Limestraël
I would have undestood the error if GameObject was also an instance of Binary (then the two instances would match), but it's not the case...
As Daniel Fischer has mentioned, presumably a Binary instance could later be written for GameObject; even if you have no intention of doing so, GHC considers the possibility. In other words, it's sufficient that such an instance could exist, not that it actually does. In general: Instance selection and context checking are separate and occur in that order, thus contexts generally can't influence instance selection (except by using OverlappingInstances and strategically confusing instance heads, ensuring that GHC can't make any sense of your code until considering the contexts). Unfortunately, anything involving extremely generic instances with some constraint tend to be very difficult to construct, because of how this works. This tends to include things like default instances for types not covered by specific ones, making all instances of X also instances of Y, fundep "type predicates" based on class membership, and so on. Type hackery can often get you most of what you want, but it gets awkward fast. - C.

Ok, so I am heading to a headache...
Daniel Fischer mentioned a solution using Type Families. As I read, those
are meant to replace the FunDeps, I will try this solution...
2010/4/17 Casey McCann
On Sat, Apr 17, 2010 at 4:01 PM, Limestraël
wrote: I would have undestood the error if GameObject was also an instance of Binary (then the two instances would match), but it's not the case...
As Daniel Fischer has mentioned, presumably a Binary instance could later be written for GameObject; even if you have no intention of doing so, GHC considers the possibility. In other words, it's sufficient that such an instance could exist, not that it actually does.
In general: Instance selection and context checking are separate and occur in that order, thus contexts generally can't influence instance selection (except by using OverlappingInstances and strategically confusing instance heads, ensuring that GHC can't make any sense of your code until considering the contexts).
Unfortunately, anything involving extremely generic instances with some constraint tend to be very difficult to construct, because of how this works. This tends to include things like default instances for types not covered by specific ones, making all instances of X also instances of Y, fundep "type predicates" based on class membership, and so on. Type hackery can often get you most of what you want, but it gets awkward fast.
- C.
participants (6)
-
Ben Millwood
-
Casey McCann
-
Daniel Fischer
-
Ivan Lazar Miljenovic
-
Limestraël
-
Sebastian Fischer