Экземпляр класса типов с функциональными зависимостями не работает - PullRequest
11 голосов
/ 16 декабря 2011

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

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 ]

18 голосов
/ 16 декабря 2011

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

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

6 голосов
/ 16 декабря 2011

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