我正在尝试整理以下课程Domain
及其实例TrivialDomain
{-# LANGUAGE TypeFamilies #-}
data Transition = Transition
class Domain d where
type Set d
type Engine d :: * -> *
top :: Engine d (Set d)
-- ...
complement :: Set d -> Engine d (Set d)
exclude :: Set d -> Set d -> Engine d (Set d)
-- ...
data TrivialDomain = TrivialDomain
instance Domain TrivialDomain where
type Set TrivialDomain = [Int]
type Engine TrivialDomain = IO
top = return [0..10]
-- ...
complement a = top >>= (flip exclude) a
exclude a b = return $ filter (not . (`elem` b)) a
-- ...
但是我一直收到以下错误,但我无法理解
test3.hs:25:21:
Couldn't match type ‘Engine d0’ with ‘IO’
The type variable ‘d0’ is ambiguous
Expected type: IO (Set d0)
Actual type: Engine d0 (Set d0)
In the first argument of ‘(>>=)’, namely ‘top’
In the expression: top >>= (flip exclude) a
test3.hs:25:35:
Couldn't match type ‘Set d1’ with ‘[Int]’
The type variable ‘d1’ is ambiguous
Expected type: Set d0 -> [Int] -> IO [Int]
Actual type: Set d1 -> Set d1 -> Engine d1 (Set d1)
In the first argument of ‘flip’, namely ‘exclude’
In the second argument of ‘(>>=)’, namely ‘(flip exclude) a’
我希望Engine d (Set d)
在实例声明中解析为IO [Int]
,但似乎并非如此。至少GHC并不这么认为。我错过了什么?
答案 0 :(得分:6)
在您的情况下,关联类型不足以推断方法的类型。
您有Domain d
班,Set
和Engine
与d
相关联。这意味着,只要我们的计划中已知d
已知Domain d
个实例,GHC就可以解析Set d
和Engine d
。但这并不会倒退。 GHC无法通过d
或Domain
解析Set d
或Engine d
个实例,因为完全有可能存在不同具有相同Domain
和Set
类型的Engine
个实例。
由于您的课程方法仅提及Set
和Engine
,因此永远无法从方法使用中推断出Domain d
。
根据你的目标,你可以做几件事。
首先,您可以d
取决于Set
和Engine
:
class Domain set engine where
type DomainOf set engine :: *
-- ...
更一般地说,FunctionalDependencies
为您提供了更强大的灵活性来强制类型之间的依赖关系。例如,您可以明确声明每个d
只有一个Set
,这足以恢复良好的类型推断:
class Domain d set engine | d -> set engine, set -> d where
top :: engine set
complement :: set -> engine set
exclude :: set -> set -> engine set
data TrivialDomain = TrivialDomain
instance Domain TrivialDomain [Int] IO where
top = return [0..10]
complement a = top >>= (flip exclude) a
exclude a b = return $ filter (not . (`elem` b)) a
最后,如果要使用原始类,则必须向方法中添加Proxy d
个参数,以使实例和关联类型可解析:
import Data.Proxy
data Transition = Transition
class Domain d where
type Set d
type Engine d :: * -> *
top :: Proxy d -> Engine d (Set d)
complement :: Proxy d -> Set d -> Engine d (Set d)
exclude :: Proxy d -> Set d -> Set d -> Engine d (Set d)
data TrivialDomain = TrivialDomain
instance Domain TrivialDomain where
type Set TrivialDomain = [Int]
type Engine TrivialDomain = IO
top _ = return [0..10]
complement d a = top d >>= (flip (exclude d)) a
exclude d a b = return $ filter (not . (`elem` b)) a
这里,Proxy d
的目的是准确指定您要使用的实例。
然而,这意味着我们必须在每个方法用法上编写top (Proxy :: Proxy d)
(与其他方法类似),这是相当繁重的。使用GHC 8,我们可以省略Proxy
并使用TypeApplications
代替:
{-# language TypeApplications, TypeFamilies #-}
-- ...
instance Domain TrivialDomain where
type Set TrivialDomain = [Int]
type Engine TrivialDomain = IO
top = return [0..10]
complement a = top @TrivialDomain >>= (flip (exclude @TrivialDomain)) a
exclude a b = return $ filter (not . (`elem` b)) a