
From: Martin DeMello
Subject: [Haskell-cafe] typeclass and functional dependency problem I'm writing a Gtk2hs app, and I have several custom widgets that are composite objects represented by records, one field of which is a container widget. I am trying to write a replacement for gtk2hs's boxPackStart
boxPackStart :: (BoxClass self, WidgetClass child) => self -> child -> Packing -> Int -> IO ()
that will accept either a gtk widget or one of my custom widgets to place in the box, and do the right thing. Here's my attempt at it; what I want to know is why the commented out bit didn't work and I had to individually add instances of widgets instead:
--------------------------------------------- -- packable objects class WidgetClass w => Packable a w | a -> w where widgetOf :: a -> w
--instance WidgetClass w => Packable w w where -- widgetOf = id
instance Packable Button Button where widgetOf = id
instance Packable Entry Entry where widgetOf = id
instance Packable Label Label where widgetOf = id
instance Packable Notebook Notebook where widgetOf = id
instance Packable HBox HBox where widgetOf = id
-- add widget to box boxPackS :: (BoxClass b, WidgetClass w, Packable a w) => b -> a -> Packing -> Int -> IO () boxPackS box child p i = boxPackStart box (widgetOf child) p i
---------------------------------------------
If I try to use
instance WidgetClass w => Packable w w where widgetOf = id
instead, I get the compilation error
Editor.hs:23:10: Functional dependencies conflict between instance declarations: instance Packable PairBox VBox -- Defined at Editor.hs:23:10-30 instance WidgetClass w => Packable w w -- Defined at GuiUtils.hs:13:10-38
even though PairBox does not belong to WidgetClass.
This is a very common problem. The line
instance WidgetClass w => Packable w w where
means "Every type is an instance of Packable by this declaration", not "Every type that has a WidgetClass instance is a Packable by this declaration", which is what you wanted. So you end up with two Packable instances for PairBox, "Packable PairBox PairBox" and "Packable PairBox Container" (or whatever type you used), which causes this conflict. This is a consequence of Haskell type classes being open. Even if you don't have a WidgetClass instance for a type (PairBox) in scope where you define this instance, one could be defined elsewhere and be in scope at a call site. Unfortunately Haskell doesn't provide a way to write what you want. I can think of a few solutions, none of which are ideal: 1. Create separate instances for each type. Writing some generating code to do this is probably the best available option. 2. Convert everything to a Container. 3. Make boxPackS a TH splice, and use a naming convention to differentiate your objects from the built-in widgets. I don't like 3) because it's fragile. Pretty much everything else is a variant of either 1 (lots of instance decls) or 2 (another function call/separate functions). John L.