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

Lan*_*dei 11 haskell typeclass functional-dependencies

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

class Pair p a | p -> a where
  one :: p -> a
  two :: p -> a
Run Code Online (Sandbox Code Playgroud)

这似乎工作得很好,例如

instance Pair [a] a where
  one [x,_] = x
  two [_,y] = y 
Run Code Online (Sandbox Code Playgroud)

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

instance Pair (a,a) a where
  one p = fst p 
  two p = snd p
Run Code Online (Sandbox Code Playgroud)

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

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)
Run Code Online (Sandbox Code Playgroud)

有没有办法正确定义实例?或者我必须诉诸newtype包装?

C. *_*ann 18

实际上你的实例工作得很好.注意:

main = print $ two (3 :: Int, 4 :: Int)
Run Code Online (Sandbox Code Playgroud)

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

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

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

instance (a ~ b) => Pair (a,b) a where
Run Code Online (Sandbox Code Playgroud)

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


fuz*_*fuz 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>
Run Code Online (Sandbox Code Playgroud)