2012-07-29 72 views
9

考慮下面的代碼示例:Haskell子類型類需要UndecidableInstances?

{-# LANGUAGE FlexibleInstances #-} 
{-# LANGUAGE UndecidableInstances #-} -- Is there a way to avoid this? 

-- A generic class with a generic function. 
class Foo a where 
    foo :: a -> a 

-- A specific class with specific functions. 
class Bar a where 
    bar :: a -> a 
    baz :: a -> a 

-- Given the specific class functions, we can implement the generic class function. 
instance Bar a => Foo a where 
    foo = bar . baz 

-- So if a type belongs to the specific class... 
instance Bar String where 
    bar = id 
    baz = id 

-- We can invoke the generic function on it. 
main :: IO() 
main = 
    putStrLn (foo "bar") 

(我的實際代碼的方式更復雜,這是最小的熬濃的情況下表現出的模式。)

這是我不清楚爲什麼這裏需要UndecidableInstances - 類型參數aBar a => Foo a的兩側出現一次,所以我期望事情「正常工作」。我顯然在這裏失去了一些東西。但無論如何,有沒有辦法做到這一點,而不使用UndecidableInstances

+2

通過聲明'實例Bar a => Foo a',您爲每個'a'引入了一個實例 - 上下文Bar a沒有用於實例選擇 - 但是如果您有重疊的實例,GHC將會找到最多在每個模塊的基礎上具體的一個。 – 2012-07-29 06:42:13

+0

這是...反直覺:-)有沒有辦法解決它?我認爲「找到最具體的實例」就是「UndecidableInstances」將會嘗試做的事情,但在我的代碼中,它失敗了。 – 2012-07-29 07:33:12

+0

相關:http://stackoverflow.com/questions/3213490 – sdcvvc 2012-07-29 09:40:38

回答

8

您可以採取幾種方法;我認爲你沒有提供足夠的背景來確定哪一個最合適。如果您使用GHC-7.4,則可能需要嘗試DefaultSignatures擴展。

{-# LANGUAGE FlexibleInstances #-} 
{-# LANGUAGE DefaultSignatures #-} 

-- A generic class with a generic function. 
class Foo a where 
    foo :: a -> a 
    default foo :: Bar a => a -> a 
    foo = bar . baz 

-- A specific class with specific functions. 
class Bar a where 
    bar :: a -> a 
    baz :: a -> a 

instance Bar String where 
    bar = id 
    baz = id 

instance Foo String 

main :: IO() 
main = 
    putStrLn (foo "bar") 

你仍然需要聲明一個類型是Foo一個實例,但你並不需要重複方法聲明,因爲默認的實現將被使用。

另一個相當輕量級的方法是使用新類型。如果您有需要Foo實例的功能,則可以將新實例換成Bar實例。

newtype FooBar a = FooBar { unFooBar :: a } 

instance Bar a => Foo (FooBar a) where 
    foo = FooBar . bar . baz . unFooBar 

-- imported from a library or something... 
needsFoo :: Foo a => a -> b 

myFunc = needsFoo (FooBar someBar) 

或者,你可以用一個正常的功能替換foo,或進行專門的版本,以獲得由Bar實例:

-- if every `Foo` is also a `Bar`, you can just do this. No need for `Foo` at all! 
foo :: Bar a => a -> a 
foo = bar . baz 

-- if most `Foo`s aren't `Bar`s, you may be able to use this function when you have a `Bar` 
fooBar :: Bar a => a -> a 
foo = bar . baz 

這可能是最好的解決方案,如果他們的工作你的情況。

另一種選擇是手動聲明每個Foo實例。雖然可能有很多不同的可能的例子,但是代碼庫通常只有少數實際使用的實例。如果這是真的,那麼僅僅寫出你需要的3或4個實例可能會少一些工作,而不是試圖實現更通用的解決方案。

作爲最後一招,您可以使用類似於您的原始代碼的東西,但您還需要OverlappingInstances才能使其工作(如果您不需要OverlappingInstances,則不需要Foo類)。這是允許GHC在有多個可用匹配時選擇「最具體實例」的擴展。這或多或少的工作,雖然你可能得不到你所期望的。

class Foo a where 
    foo :: a -> a 

class Bar a where 
    bar :: a -> a 
    baz :: a -> a 

instance Bar String where 
    bar = id 
    baz = id 

instance Bar a => Foo a where 
    foo = bar . baz 

instance Foo [a] where 
    foo _ = [] 

main :: IO() 
main = 
    print (foo "foo") 

現在main打印一個空字符串。有兩個Foo實例,分別爲a[a]。後者更具體,所以它被選爲foo "foo",因爲字符串的類型爲[Char],儘管您可能想要前者。所以,現在你也需要寫

instance Foo String where 
    foo = bar . baz 

此時你不妨離開了Bar a => Foo a實例完全。

+0

哇,謝謝你的詳細解答! DefaultSignatures不適用於我,因爲它們需要在泛型類中聲明默認值,並且我希望人們能夠自由定義新的特定類。 newtype方法可能適用於我,但我需要將其適應於我更復雜的情況。使用函數對我來說不起作用...... OverlappingInstances也可能起作用,因爲在我的情況下,你描述的類型重疊的機會很小。如果有效,最好是因爲它侵入性最小。再次感謝! – 2012-07-29 11:19:53

+0

@ OrenBen-Kiki - 我無法想象任何方式,默認簽名會限制'Foo'的進一步使用,無論它涉及實例,子類還是在其他無關函數中使用'foo'方法。目前還不清楚你想要在這裏建模什麼,但是我懷疑你正在嘗試使用類型類來設置一個OOP風格的層次結構。如果是這樣,我建議你問另一個問題,要求在Haskell中建模你的問題。 – 2012-07-29 13:06:39

+0

也是一個警告的話。如果你決定採用'OverlappingInstances'路線,那麼在某些時候,GHC可能會抱怨它需要'IncoherentInstances'啓用。不要這樣做,這將無濟於事。 – 2012-07-29 13:13:10

0

除了上面的答案。來自base庫的Data.Traversable模塊中使用的政治很有吸引力。簡而言之,在庫中給出通用實例迫使最終用戶接受你的決定,這並不總是最好的做法。 Data.Traversable包含像foldMapDefault這樣的函數,它給出了默認的實現,但具體實現的決定仍然取決於用戶。