玩弄类型类我想出了看似无辜的
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
包装器?
答案 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>