类型推断如何在存在功能依赖性的情况下工作

时间:2012-09-13 13:18:06

标签: haskell types functional-programming type-inference functional-dependencies

请考虑以下代码:

{-# 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支持重叠实例,不支持类型系列。   但我现在不会接受这一点。

我仍然不明白这是什么意思。所以仍然在寻找更好的理解答案。

1 个答案:

答案 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 -> aInt -> 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 aFloat)。

你想要

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)不匹配。