如何将Haskell类型强制为更通用的类型

Rou*_*len 3 haskell types gtk2hs

我正在努力让以下代码超过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
Run Code Online (Sandbox Code Playgroud)

即使乍一看它看起来像是一个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'
Run Code Online (Sandbox Code Playgroud)

类型containerGetChildren是:

containerGetChildren :: ContainerClass self => self -> IO [Widget]
Run Code Online (Sandbox Code Playgroud)

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

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

TIA

And*_*ewC 8

不,没有办法.你的脱臼

getFirstChild :: (WidgetClass w1, WidgetClass w2) => w1 -> IO (Maybe w2)
Run Code Online (Sandbox Code Playgroud)

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

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

data MyWodget = ....
instance WidgetClass MyWodget where ....
Run Code Online (Sandbox Code Playgroud)

那么,Maybe MyWodget只要我将一些MyWodge放在另一个MyWodget中,你就可以从你的类型签名中期望你的函数可以返回一个,但是因为你在定义中使用的函数只能用于Widgets,Haskell不能在MyWodget上使用它,我的代码既可以进行类型检查,也可以进行类型检查.

你不会成为能够与unsafeCoerce解决这个问题,因为MyWodget根本是不以任何方式Widget的; haskell类型类允许您使用具有相同功能界面的真正不同的数据类型,因此您不能仅仅强制执行.

你可以定义自己的类

class FromWidget w where -- not a good name, I admit
    fromWidget :: Widget -> w
Run Code Online (Sandbox Code Playgroud)

然后你可以使用fromWidget重写你的函数更通用:

getFirstChild :: (WidgetClass w1, FromWidget w2) => w1 -> IO (Maybe w2)
Run Code Online (Sandbox Code Playgroud)

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

instance FromWidget MyWodget where ....
Run Code Online (Sandbox Code Playgroud)

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

  • 感谢您的回答,我做了一些检查,似乎我可以通过返回IO(Maybe Widget)来代替更通用的w2类型. (2认同)