我的特殊问题可能与存在类型有关,但我不确定所以我不会把它放在标题中。
无论如何,这就是我要做的事情。
拥有包含异构组件列表的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 ()
我如何做我想做的事?
答案 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
确定。在这种情况下,第一个表单将编译。