显式签名中单子绑定的限制

3

假设我有以下函数:

loadDialog :: String -> IO MyDialog
loadDialog file = do
  Just ui <- xmlNew file
  MyDialog
    <$> xmlGetWidget ui castToWindow "w"
    <*> xmlGetWidget ui castToButton "b"

在哪里

xmlGetWidget :: WidgetClass widget => GladeXML -> (GObject -> widget) -> String -> IO widget

现在我想捕捉以下xmlNew/xmlGetWidget的使用模式:
widgetBinder :: WidgetClass widget => FilePath -> IO ((GObject -> widget) -> String -> IO widget)
widgetBinder file = do
  Just ui <- xmlNew file
  return $ xmlGetWidget ui

这应该让我可以写:

loadDialog file = do
  bind <- widgetBinder file
  MyDialog
    <$> bind castToWindow "w"
    <*> bind castToButton "b"

问题是,它无法通过类型检查(具体错误请看这里)。我认为可以明确地为绑定提供通用签名,但似乎对于单子绑定不适用,因为即使使用RankNTypes,以下代码也无法通过类型检查(错误请看这里):

loadDialog file = do
  bind :: WidgetClass w => (GObject -> w) -> String -> IO w
       <- widgetBinder file
  MyDialog
    <$> bind castToWindow "w"
    <*> bind castToButton "b"

我能帮忙做些什么吗?


我们能获取到具体的错误信息吗? - YellPika
2个回答

3
一个笨重但可行的解决方案是将您的函数放入一个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"

或类似的东西...

哇,它能工作了,而且在调用现场使用起来非常舒适。再次感谢。 - aemxdp

2
很可能是因为在castToWindowcastToButton之间的widget具体选择不同,导致类型检查器试图确定bind的类型时,在两种设置中使用了应用程序的信息并发现它们冲突。换句话说,多态性太少了。
为了避免这种情况,您需要一个明确的签名和RankNTypes,就像您尝试过的那样。
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'

请注意,输入函数内部包含的forall确保该函数是以多态方式定义并在每个站点单独实例化。
作为一个更简单的例子,我们可以尝试(但失败)定义poly
poly :: (Int, Double)
poly = let f = (+1)
       in (f 1, f 1.0)         -- typecheck fail!

出现错误,因为我们无法统一IntDouble,但必须这样做才能对(+1)进行类型。然而,如果我们明确要求类型检查器延迟实例化(+1),则不会出现错误。

poly :: (forall n . Num n => n -> n) -> (Int, Double)
poly f = (f 1, f 1.)

>>> poly (+1)
(2, 2.0)

我们很好。

谢谢,但它也不起作用:http://pastebin.com/96mCr9e4。顺便说一下,似乎单子绑定应该解糖成等效的东西(...>>= \(bind :: forall w . ...) -> ...)。 - aemxdp
它应该以类似于newtype解决方案的方式可行,但可能需要更改其他注释。newtype更安全,因为如果绑定函数在任何地方被实例化,则无法将其包装到newtype中。 - J. Abrahamson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接