3

我正在努力让以下代码通过 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

尽管乍一看它看起来像是一个 Gtk2hs 问题,但它实际上是关于 Haskell 类型系统的。

当我尝试使用 GHC 编译此代码时,我收到以下错误消息:

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'

的类型containerGetChildren是:

containerGetChildren :: ContainerClass self => self -> IO [Widget]

Widget类型本身就是 的实例WidgetClass,所以我不明白为什么我不能getFirstChild指定函数的返回类型,因为w2它是 的实例WidgetClass

有没有办法在 Haskell 中表达这一点而不使用类似的东西unsafeCoerce

TIA

4

2 回答 2

8

不,没有办法。你的声明

getFirstChild :: (WidgetClass w1, WidgetClass w2) => w1 -> IO (Maybe w2)

说你的函数可以返回任何w2,只要它在 WidgetClass 中,但这是一个谎言,因为它只返回 Widget。Haskell 不会让你这样误导程序员。

假设我正在导入你的模块并写

data MyWodget = ....
instance WidgetClass MyWodget where ....

那么我可以合理地从您的类型签名中期望您的函数可以返回 a Maybe MyWodget,只要我将一些 MyWodget 放在另一个 MyWodget 中,但是因为您在定义中使用的函数只能与小部件一起使用,Haskell不能在 MyWodget 上使用它,而且我的代码既会进行类型检查,也不会进行类型检查。

您将无法使用unsafeCoerce 解决此问题,因为 MyWodget 根本不是 Widget;haskell 类型类允许您使用具有相同功能接口的真正不同的数据类型,因此您不能只是在那里强制。

你可以定义你自己的类

class FromWidget w where -- not a good name, I admit
    fromWidget :: Widget -> w

然后您可以使用 fromWidget 将您的函数重写为更通用:

getFirstChild :: (WidgetClass w1, FromWidget w2) => w1 -> IO (Maybe w2)

只要我做,我就可以在 MyWodget 上使用它

instance FromWidget MyWodget where ....

但我非常怀疑是否有办法以这种方式制作 WidgetClass 中的所有内容。也许你可以制作你感兴趣的每一个 WidgetClass 东西,但也许你应该检查一下你是否真的只对 Widgets 感兴趣。

于 2012-09-15T08:21:30.863 回答
2

根据您的功能类型,您的功能应该适用于任何w1w2属于WidgetClass。但是您返回的是更具体的类型Widget。每当您有这种疑问时,请不要向函数提供类型并查看ghci推断的类型。ghci总是会推断出更一般的类型。

于 2012-09-15T08:22:04.337 回答