Haskell 类型族中的类型歧义
Type ambiguity in Haskell type families
我正在尝试将以下 class 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不这么认为。我错过了什么?
在您的情况下,关联类型不足以推断方法的类型。
您有 class Domain d
,并且 Set
和 Engine
关联到 d
。这意味着只要我们的程序中有一个已知的 d
和一个已知的 Domain d
实例,GHC 就可以解析 Set d
和 Engine d
。但这不会倒退。 GHC 无法解析存在 Set d
或 Engine d
的 d
或 Domain
实例,因为完全有可能存在不同的 Domain
实例具有相同的 Set
和 Engine
类型。
由于您的 class 方法仅提及 Set
和 Engine
,因此永远无法从方法使用中推断出 Domain d
。
您可以根据自己的目标做几件事。
首先,您可以使 d
依赖于 Set
和 Engine
:
class Domain set engine where
type DomainOf set engine :: *
-- ...
更一般地说,FunctionalDependencies
使您可以更灵活地强制执行类型之间的依赖关系。比如可以具体声明每个Set
只有一个d
,这样就可以恢复好的类型推断了:
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
最后,如果您想使用原来的 class,您必须将 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
s 并使用 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
我正在尝试将以下 class 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不这么认为。我错过了什么?
在您的情况下,关联类型不足以推断方法的类型。
您有 class Domain d
,并且 Set
和 Engine
关联到 d
。这意味着只要我们的程序中有一个已知的 d
和一个已知的 Domain d
实例,GHC 就可以解析 Set d
和 Engine d
。但这不会倒退。 GHC 无法解析存在 Set d
或 Engine d
的 d
或 Domain
实例,因为完全有可能存在不同的 Domain
实例具有相同的 Set
和 Engine
类型。
由于您的 class 方法仅提及 Set
和 Engine
,因此永远无法从方法使用中推断出 Domain d
。
您可以根据自己的目标做几件事。
首先,您可以使 d
依赖于 Set
和 Engine
:
class Domain set engine where
type DomainOf set engine :: *
-- ...
更一般地说,FunctionalDependencies
使您可以更灵活地强制执行类型之间的依赖关系。比如可以具体声明每个Set
只有一个d
,这样就可以恢复好的类型推断了:
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
最后,如果您想使用原来的 class,您必须将 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
s 并使用 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