Ваш экземпляр работает просто отлично, на самом деле.Обратите внимание:
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
работает как есть, без аннотаций.