了解类型约束

时间:2014-12-02 00:19:38

标签: haskell types typeclass existential-type

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

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

拥有包含异构组件列表的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

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

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

现在,我想尝试一下。我创建了一个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)对于任何类型a都为真,则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 ()

我如何做我想做的事?

1 个答案:

答案 0 :(得分: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确定。在这种情况下,第一个表单将编译。