Question

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?

Was it helpful?

Solution

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...

OTHER TIPS

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top