2014-12-02 121 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