jak*_*iel 4 haskell type-inference type-families
我正在尝试将以下类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
-- ...
Run Code Online (Sandbox Code Playgroud)
但我一直得到以下错误,我无法理解
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’
Run Code Online (Sandbox Code Playgroud)
我希望在实例声明中Engine d (Set d)解决IO [Int],但似乎并非如此.至少GHC并不这么认为.我错过了什么?
在您的情况下,关联类型不足以推断方法的类型.
你有一流的Domain d,并Set和Engine关联到d.这意味着只要d我们的程序中已知具有已知Domain d实例,GHC就可以解析Set d和Engine d.但这不会倒退.GHC无法通过a 或an 的存在来解析d或Domain实例,因为完全可能存在具有相同和类型的不同实例.Set dEngine dDomainSetEngine
由于类方法只提Set和Engine,Domain d 不能从方法的使用来推断.
根据你的目标,你可以做几件事.
首先,你可以d依赖Set和Engine:
class Domain set engine where
type DomainOf set engine :: *
-- ...
Run Code Online (Sandbox Code Playgroud)
更一般地说,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
Run Code Online (Sandbox Code Playgroud)
最后,如果要使用原始类,则必须向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
Run Code Online (Sandbox Code Playgroud)
这里,目的Proxy d是准确指定要使用的实例.
但是,这意味着我们必须编写top (Proxy :: Proxy d)每个方法的用法(与其他方法类似),这是相当繁重的.使用GHC 8,我们可以省略Proxys并使用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
Run Code Online (Sandbox Code Playgroud)