2014-12-02 149 views
0

我的特殊问题可能与存在类型有关,但我不确定,所以我不会把它放在标题中。理解类型约束

无论如何,这就是我想要做的。

有一个Entity类型,它包装在一个异构的组件列表上。然后,我有一个HasComponent a b类型类别,表示b列表中包含a类型的组件。以下是我编写它的方式和类实例。

data Entity c = Entity c 

data CompNode c n = CompNode c n 
data CompEnd = CompEnd 

class HasComponent a b where 
    getComponent :: b -> a 

instance HasComponent a (CompNode a n) where 
    getComponent (CompNode a _) = a 

instance HasComponent a n => HasComponent a (CompNode b n) where 
    getComponent (CompNode _ n) = getComponent n 

instance HasComponent a b => HasComponent a (Entity b) where 
    getComponent (Entity b) = getComponent b 

还有为Entity一个HasComponent实例。这只是为了方便。

到目前为止,一切都在编译。

现在,我想尝试一下。我制作了一个DisplayData a类型,其中包含一些类型为a的数据,用于显示。这是组件之一。然后,我制作了Displayer a,它是a -> IO()类型函数的包装。该组件旨在提供一种显示数据的方式。

data DisplayData a = DisplayData a 
data Displayer a = Displayer (a -> IO()) 

现在,这两个组件应该很好地结合在一起。我想写一个函数display,它需要一个满足一些约束条件的Entity并显示它。

这是我尝试

display :: (HasComponent (DisplayData a) c, HasComponent (Displayer a) c) => Entity c -> IO() 
display e = f a 
    where Displayer f = getComponent e :: Displayer a 
      DisplayData a = getComponent e :: DisplayData a 

我想这是什么意思是:“如果存在某种类型,使得(HasComponent (DisplayData a) c, HasComponent (Displayer a) c)是真的,那么display可以采取Entity c并产生一个IO动作。”

我认为,这可能意味着的却是:“如果(HasComponent (DisplayData a) c, HasComponent (Displayer a) c)为任何和所有类型的真,那么display可以采取Entity c并产生一个IO动作

我得到的错误是这个

。 ?
Could not deduce (HasComponent (DisplayData a0) c) 
    arising from the ambiguity check for `display' 
from the context (HasComponent (DisplayData a) c, 
        HasComponent (Displayer a) c) 
    bound by the type signature for 
      display :: (HasComponent (DisplayData a) c, 
         HasComponent (Displayer a) c) => 
         Entity c -> IO() 
    at Components.hs:24:12-94 
The type variable `a0' is ambiguous 
In the ambiguity check for: 
    forall c a. 
    (HasComponent (DisplayData a) c, HasComponent (Displayer a) c) => 
    Entity c -> IO() 
To defer the ambiguity check to use sites, enable AllowAmbiguousTypes 
In the type signature for `display': 
    display :: (HasComponent (DisplayData a) c, 
       HasComponent (Displayer a) c) => 
      Entity c -> IO() 

我怎么做我想在这里

回答

2

首先,从函数体中引用的类型签名类型变量,您需要启用ScopedTypeVariables和FORALL添加到类型:

{-# LANGUAGE ScopedTypeVariables #-} 

display :: forall a c . (HasComponent (DisplayData a) c, HasComponent (Displayer a) c) 
     => Entity c -> IO() 
display e = f a 
    where 
     Displayer f = getComponent e :: Displayer a 
     DisplayData a = getComponent e :: DisplayData a 

但是,这仍然会产生一个错误。问题在于a类型仅在上下文中提及,而不是实际类型。编译器将无法实例化此类型。你有几个选择。

您可以添加它包含了类型“虚拟”参数:

display :: forall a c proxy . 
      (HasComponent (DisplayData a) c, HasComponent (Displayer a) c) 
     => proxy a -> Entity c -> IO() 
display _ e = f a where ... 

或者,你可以添加一个函数依赖于你的类,以及一些杂注:

{-# LANGUAGE OverlappingInstances, UndecidableInstances #-} 

class HasComponent a b | b -> a where 

它说,类型a由类型b决定。在这种情况下,第一种形式将被编译。

+0

是否有任何我应该知道的功能依赖的副作用? – 2014-12-02 09:59:07

+0

您可以编写的一组实例较小。功能依赖本身没有任何其他副作用。为了使函数依赖对你的实例起作用,你需要UndecidableInstances,它有副作用,你可以在类型检查器中有非终结符。对于您的具体情况,不终止将不会发生。 – user2407038 2014-12-02 14:51:24