11

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

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包装?

4

2 回答 2

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将按原样工作,无需注释。

于 2011-12-16T15:52:42.973 回答
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>
于 2011-12-16T15:52:59.867 回答