请考虑以下代码:
{-# LANGUAGE MultiParamTypeClasses,FlexibleInstances,FunctionalDependencies,UndecidableInstances,FlexibleContexts #-}
class Foo a c | a -> c
instance Foo Int Float
f :: (Foo Int a) => Int -> a
f = undefined
现在我在ghci中看到f的推断类型
> :t f
> f :: Int -> Float
现在如果我添加以下代码
g :: Int -> Float
g = undefined
h :: (Foo Int a) => Int -> a
h = g
我收到错误
Could not deduce (a ~ Float)
我无法理解这里发生了什么?限制Foo Int a
应该将h
的类型限制为Int -> Float
,如f
的推断类型所示。
是因为在解析实例之前发生了类型统一吗?
[更新]
Dan Doel在咖啡馆邮件列表上的解释
答案,我相信,这是fundep之间的区别 实现和类型系列是局部约束信息。 Fundeps没有本地传播。因此,在您的第一个定义中,您已在本地提供了“
Int -> a
”,其中 GHC可以接受。然后它从外部计算出函数 “(Foo Int a) => Int -> a
”实际上是Int -> Float
。在第二个定义中,您尝试提供“
}Int -> Float
”,但是 GHC只在本地知道您需要提供“Int -> a
” 约束'Foo Int a
'这不是fundeps所固有的。人们可以制作一个fundeps版本 具有局部约束规则(通过转换为 新型家庭的东西)。但是,差异也是原因 fundeps支持重叠实例,不支持类型系列。 但我现在不会接受这一点。
我仍然不明白这是什么意思。所以仍然在寻找更好的理解答案。
答案 0 :(得分:4)
要避免此问题,您可以改为使用类型系列:
{-# LANGUAGE TypeFamilies, FlexibleContexts #-}
class Fooo a where
type Out a :: *
instance Fooo Int where
type Out Int = Float
ff :: (Foo Int) => Int -> (Out Int)
ff = undefined
gg :: Int -> Float
gg= undefined
hh :: (Foo Int) => Int -> (Out Int)
hh = gg
类型家庭很好。尽可能使用类型系列!
我猜你的错误是因为ghc可以从f :: Int -> Float
以及您的类定义和实例中推断f :: Foo Int a => Int -> a
,但它无法从g :: Foo Int a => Int -> a
中推断g:: Int -> Float
。
将约束视为函数的秘密字典参数很方便。在f中存在固有但受约束的多态性,而不存在g。
我认为有必要注意ghci给出的基本上完全相同的错误信息就好像我们试图定义一样
j :: Int -> Float
j = undefined
k :: Eq a => Int -> a
k = j
很明显,这不应该起作用,因为我们知道k
在其第二个参数中应该具有多态性。 ghc尝试将类型Int -> a
与Int -> Float
匹配并失败,抱怨它无法从上下文a ~ Float
推断出Eq a
,即使有一个实例Eq Float
}。在您的示例中,它表示即使有a ~ Float
的实例,它也无法从上下文Foo Int a
推断Foo Int Float
。我意识到我们可以推断出a
只有一种可能的类型,但是通过为Foo创建类和实例,你已经定义了一个关系并断言它是一个函数依赖。这与定义函数不同(这就是类型族解决问题的原因 - 它定义了函数)。
ghc在写作时也抱怨
aconst :: (Foo Int a) => a
aconst = 0.0
甚至
anotherconst :: (Foo Int a) => a
anotherconst = 0.0::Float
总是因为它无法匹配您想要的约束多态 a
与您提供的特定类型(Fractional a
或Float
)。
你想要
forall a.Foo Int a
与Float
的类型相同,但事实并非如此。只有一种类型满足forall a.Foo Int a
,它是Float
,因此ghci可以f::forall a.(Foo Int a)=>a->Float
并使用f::Int->Float
推导(使用Foo的字典),但是你期待ghci取Float
并发现它是forall a.Foo Int a
,但是Float没有字典,它是一个类型,而不是一个类型类。 ghci可以单向做,但不能做到。
Dan关于本地信息的观点是,ghc必须同时使用Foo
的定义和你推断的实例来推断Float
可以重写forall a.(Foo Int a)
,并且在此在汇编中,ghc没有使用全局信息,因为它只是试图进行匹配。我的观点是,Float
匹配forall a.(Foo Int a)
但forall a.(Foo Int a)
与Float
不匹配,因为"this"
与模式(x:xs)
匹配但{ {1}}与模式(x:xs)
不匹配。