Haskell, поливариадная функция и вывод типа
При поиске примеров поливариадных функций я нашел этот ресурс: Stackru: как создать поливариадную функцию haskell? и был фрагмент ответа, подобный этому:
class SumRes r where
sumOf :: Integer -> r
instance SumRes Integer where
sumOf = id
instance (Integral a, SumRes r) => SumRes (a -> r) where
sumOf x = sumOf . (x +) . toInteger
Тогда мы могли бы использовать:
*Main> sumOf 1 :: Integer
1
*Main> sumOf 1 4 7 10 :: Integer
22
*Main> sumOf 1 4 7 10 0 0 :: Integer
22
*Main> sumOf 1 4 7 10 2 5 8 22 :: Integer
59
Я попытался немного его изменить, просто для любопытства, потому что на первый взгляд это показалось мне довольно сложным, и я понял это:
class SumRes r where
sumOf :: Int -> r
instance SumRes Int where
sumOf = id
instance (SumRes r) => SumRes (Int -> r) where
sumOf x = sumOf . (x +)
Я только что изменился Integer
в Int
и повернулся instance (Integral a, SumRes r) => SumRes (a -> r) where
менее полиморфный к instance (SumRes r) => SumRes (Int -> r) where
Чтобы скомпилировать это я должен был установить XFlexibleInstances
флаг. Когда я попытался проверить sumOf
Функция у меня возникла проблема:
*Main> sumOf 1 :: Int
1
*Main> sumOf 1 1 :: Int
<interactive>:9:9
No instance for (Num a0) arising from the literal `1'
The type variable `a0' is ambiguous...
Тогда я попробовал:
*Main> sumOf (1 :: Int) (1 :: Int) :: Int
2
Почему Хаскелл не может сделать вывод, что мы хотим Int
в этой ситуации, учитывая, что мы используем Int
в нашем SumRes
класс типов?
2 ответа
Экземпляр
instance (...) => SumRes (Int -> r) where
примерно означает "вот как определить SumRes
на Int -> r
для любого r
(при определенных условиях)". Сравните это с
instance (...) => SumRes (a -> r) where
что означает "вот как определить SumRes
на a -> r
для любого a,r
(при определенных условиях)".
Основным отличием является то, что второй утверждает, что это соответствующий экземпляр, какой бы тип a,r
возможно. За исключением некоторого (очень хитрого и потенциально опасного) расширения Haskell, нельзя добавить больше экземпляров позже при включении функций. Вместо этого первый оставляет место для новых экземпляров, таких как, например,
instance (...) => SumRes (Double -> r) where ...
instance (...) => SumRes (Integer -> r) where ...
instance (...) => SumRes (Float -> r) where ...
instance (...) => SumRes (String -> r) where ... -- nonsense, but allowed
Это связано с тем, что числовые литералы, такие как 5
являются полиморфными: их тип должен быть выведен из контекста. Поскольку позже компилятор может найти, например, Double -> r
экземпляр и выбрать Double
как литеральные типы, компилятор не фиксирует Int -> r
экземпляр, и сообщает о неоднозначности в ошибке типа.
Обратите внимание, что при использовании некоторых (безопасных) расширений Haskell (таких как TypeFamilies
), можно "пообещать" компилятору, что ваш Int -> r
это единственный, который будет объявлен во всей программе. Это сделано так:
instance (..., a ~ Int) => SumRes (a -> r) where ...
Это обещает обработать все случаи "функционального типа", но требует, чтобы a
на самом деле тот же тип, что и Int
,
Числовые литералы сами по себе полиморфны, а не имеют тип Int
*Main> :t 1
1 :: Num a => a
Посмотрите, что происходит, когда мы получаем сигнатуру типа:
*Main> :t sumOf 1 2 3
sumOf 1 2 3 :: (Num a, Num a1, SumRes (a -> a1 -> t)) => t
Обратите внимание, что тип не упоминает Int
совсем. Средство проверки типов не может понять, как на самом деле вычислить сумму, потому что ни один из определенных Int
примеры достаточно общие, чтобы подать заявку здесь.
Если вы исправите типы, которые будут Int
тогда вы в конечном итоге
*Main> :t sumOf (1 :: Int) (2 :: Int) (3 :: Int)
sumOf (1 :: Int) (2 :: Int) (3 :: Int) :: SumRes t => t
*Main> :t sumOf (1 :: Int) (2 :: Int) (3 :: Int) :: Int
sumOf (1 :: Int) (2 :: Int) (3 :: Int) :: Int
Обратите внимание, что SumRes t => t
совместим с Int
потому что у нас есть SumRes Int
экземпляр, но если мы не укажем явно Int
, тогда у нас нет достаточно общих примеров, чтобы подать заявку здесь, потому что нет общего SumRes t
пример.