具有功能依赖性的Typeclass实例不起作用

时间:2011-12-16 15:41:46

标签: haskell typeclass functional-dependencies

玩弄类型类我想出了看似无辜的

class Pair p a | p -> a where
  one :: p -> a
  two :: p -> a

这似乎工作正常,例如

instance Pair [a] a where
  one [x,_] = x
  two [_,y] = y 

但是我为元组遇到了麻烦。即使以下定义编译......

instance Pair (a,a) a where
  one p = fst p 
  two p = snd p

......我无法按预期使用它:

main = print $ two (3, 4)

No instance for (Pair (t, t1) a)
  arising from a use of `two' at src\Main.hs:593:15-23
Possible fix: add an instance declaration for (Pair (t, t1) a)
In the second argument of `($)', namely `two (3, 4)'
In the expression: print $ two (3, 4)
In the definition of `main': main = print $ two (3, 4)

有没有办法正确定义实例?或者我是否必须使用newtype包装器?

2 个答案:

答案 0 :(得分:18)

实际上你的实例工作得很好。观察:

main = print $ two (3 :: Int, 4 :: Int)

这可以按预期工作。那么为什么没有类型注释就行不通呢?好吧,考虑一下元组的类型:(3, 4) :: (Num t, Num t1) => (t, t1)。因为数字文字是多态的,所以都不需要它们是相同的类型。该实例是为(a, a)定义的,但该实例的存在不会告诉GHC统一类型(出于各种好的理由)。除非GHC可以通过其他方式推断这两种类型是相同的,否则它将不会选择您想要的实例,即使这两种类型可以相等。

要解决您的问题,您可以添加类型注释,就像我上面所做的那样。如果参数来自其他地方,它通常是不必要的,因为它们已经知道它们是相同的类型,但如果你想使用数字文字,它会很快变得笨拙。

另一种解决方案是注意,由于实例选择的工作原理,拥有(a, a)的实例意味着即使您想要也不能编写像(a, b)这样的实例。所以我们可以作弊,使用类型强制统一,如下所示:

instance (a ~ b) => Pair (a,b) a where

我认为这需要TypeFamilies上下文的~扩展名。这样做允许实例首先匹配任何元组,因为实例选择忽略了上下文。然而,在选择实例之后,a ~ b上下文断言类型相等,如果它们不同则会产生错误但是 - 更重要的是 - 如果可能的话将统一类型变量。使用此选项,您对main的定义按原样工作,无需注释。

答案 1 :(得分:6)

问题是文字数字具有多态类型。对于类型检查者来说,两个文字都应该具有相同的类型(Int)并不明显。如果您使用的元组不是多态的,那么您的代码应该可行。请考虑以下示例:

*Main> two (3,4)

<interactive>:1:1:
    No instance for (Pair (t0, t1) a0)
      arising from a use of `two'
    Possible fix: add an instance declaration for (Pair (t0, t1) a0)
    In the expression: two (3, 4)
    In an equation for `it': it = two (3, 4)
*Main> let f = id :: Int -> Int -- Force a monomorphic type
*Main> two (f 3,f 4)
4
*Main> two ('a','b')
'b'
*Main> two ("foo","bar")
"bar"
*Main> two (('a':),('b':)) "cde"
"bcde"
*Main>