Most likely this is occurring because the concrete choice of widget
differs between castToWindow
and castToButton
. When the type-checker tries to determine the type of bind
it tries to use information about its application in both settings and see that they conflict. In other words, there is too little polymorphism.
To avoid this, you will need an explicit signature and RankNTypes
, as you've tried.
loadDialogue' :: (forall w. -> WidgetClass w => (GObject -> w) -> String -> IO w)
-> IO MyDialogue
loadDialogue' bind = MyDialogue
<$> bind castToWindow "w"
<*> bind castToButton "b"
loadDialogue :: String -> IO MyDialogue
loadDialogue file = widgetBinder file >>= loadDialogue'
Note that the forall
contained locally to the input function ensures that that function is defined polymorphically and thus instantiated individually at each site.
As a simpler example, we can try (and fail) to define poly
poly :: (Int, Double)
poly = let f = (+1)
in (f 1, f 1.0) -- typecheck fail!
which errors out because we cannot unify Int
and Double
, yet must in order to type (+1)
. If we explicitly ask for the typechecker to delay instantiating (+1), however
poly :: (forall n . Num n => n -> n) -> (Int, Double)
poly f = (f 1, f 1.)
>>> poly (+1)
(2, 2.0)
We are fine.