Reputation: 768
I am struggling to get the following code past GHC:
getFirstChild :: (WidgetClass w1, WidgetClass w2) => w1 -> IO (Maybe w2)
getFirstChild parent = do
-- check if parent is a container
if parent `isA` gTypeContainer
-- if parent is a container get the first child
then do children <- containerGetChildren $! castToContainer parent
return $! Just $! children !! 0
else return Nothing
Even though at first glance it looks like a Gtk2hs question, it is really about the Haskell type system.
When I try to compile this code with GHC, I get the following error message:
Could not deduce (w2 ~ Widget)
from the context (WidgetClass w1, WidgetClass w2)
bound by the type signature for
getFirstChild :: (WidgetClass w1, WidgetClass w2) =>
w1 -> IO (Maybe w2)
at HsFu\Gtk\Widget.hs:(6,4)-(12,28)
`w2' is a rigid type variable bound by
the type signature for
getFirstChild :: (WidgetClass w1, WidgetClass w2) =>
w1 -> IO (Maybe w2)
at HsFu\Gtk\Widget.hs:6:4
Expected type: [w2]
Actual type: [Widget]
In the first argument of `(!!)', namely `children'
In the second argument of `($!)', namely `children !! 0'
The type of containerGetChildren
is:
containerGetChildren :: ContainerClass self => self -> IO [Widget]
The Widget
type is itself an instance of WidgetClass
, so I do not understand why I cannot have the return type of the getFirstChild
function specified as w2
that is an instance of WidgetClass
.
Is there a way to express this in Haskell without using things like unsafeCoerce
?
TIA
Upvotes: 3
Views: 315
Reputation: 32455
No, there isn't a way. Your declraration
getFirstChild :: (WidgetClass w1, WidgetClass w2) => w1 -> IO (Maybe w2)
says that your function can return any w2 as long as it's in WidgetClass, but that's a lie, because it only returns Widgets. Haskell won't let you mislead the programmer like this.
Suppose that I were importing your module and wrote
data MyWodget = ....
instance WidgetClass MyWodget where ....
Then it would be reasonable for me to expect from your type signature that your function could return a Maybe MyWodget
as long as I'd put some MyWodgets inside another MyWodget, but because the function you've used in your definition can only work with Widgets, Haskell can't use it on a MyWodget, and my code would both typecheck and not typecheck.
You won't be able to fix this with unsafeCoerce because a MyWodget simply isn't a Widget in any way; haskell typeclasses let you use truly different data types with the same functional interface, so you can't just coerce there.
You could define your own class
class FromWidget w where -- not a good name, I admit
fromWidget :: Widget -> w
Then you could use fromWidget to rewrite your function to be more general:
getFirstChild :: (WidgetClass w1, FromWidget w2) => w1 -> IO (Maybe w2)
and I could use it on MyWodget as long as I make
instance FromWidget MyWodget where ....
But I deeply doubt that there's a way of making everything in WidgetClass this way. Maybe you can make every WidgetClass thing you're interested in, but maybe you should check if you're actually only interested in Widgets anyway.
Upvotes: 8
Reputation: 11208
According to the type of your function your function should work for any w1
and w2
belonging to WidgetClass
. But you are returning a more specific type Widget
. Whenever you are in such kind of doubts, don't provide the type to the function and see what type ghci
infers. ghci
will always infer the more general type.
Upvotes: 2