Экземпляр Typeclass с функциональными зависимостями не работает

Играя с типами классов, я придумал, казалось бы, невиновный

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?

Ответ 1

На самом деле ваш экземпляр работает нормально. Обратите внимание:

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 работает как есть, без аннотаций.

Ответ 2

Проблема заключается в том, что литеральное число имеет полиморфный тип. Для typechecker не очевидно, что оба литерала должны иметь один и тот же тип (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>