Let's say I have the following function:
loadDialog :: String -> IO MyDialog
loadDialog file = do
Just ui <- xmlNew file
MyDialog
<$> xmlGetWidget ui castToWindow "w"
<*> xmlGetWidget ui castToButton "b"
where
xmlGetWidget :: WidgetClass widget => GladeXML -> (GObject -> widget) -> String -> IO widget
Now I want to capture the following xmlNew/xmlGetWidget usage pattern:
widgetBinder :: WidgetClass widget => FilePath -> IO ((GObject -> widget) -> String -> IO widget)
widgetBinder file = do
Just ui <- xmlNew file
return $ xmlGetWidget ui
Which should allow me to write:
loadDialog file = do
bind <- widgetBinder file
MyDialog
<$> bind castToWindow "w"
<*> bind castToButton "b"
The problem is, it doesn't typecheck (exact error here). I've thought it's possible to provide generic signatures to bindings explicitly, but it seems this is not the case for monadic bindings since the following doesn't typecheck as well (even with RankNTypes, error here):
loadDialog file = do
bind :: WidgetClass w => (GObject -> w) -> String -> IO w
<- widgetBinder file
MyDialog
<$> bind castToWindow "w"
<*> bind castToButton "b"
Is there anything I can do?
A clunky-but-workable solution is to throw your function into a newtype:
newtype Binder = Binder (forall w. WidgetClass w => (GObject -> w) -> String -> IO w)
widgetBinder :: FilePath -> IO Binder
widgetBinder file = do
Just ui <- xmlNew file
return $ Binder (xmlGetWidget ui)
loadDialog file = do
Binder bind <- widgetBinder file
MyDialog
<$> bind castToWindow "w"
<*> bind castToButton "b"
Or something like that...
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With