Поливариадная обобщенная сумма

Этот ответ демонстрирует поливариадную функцию, которая суммирует свои аргументы:

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

Я создал обобщенную версию этой функции для всех членов Num:

class (Num n) => MySumType n r where
    mySum :: n -> r

instance (Num n) => MySumType n n where
    mySum x = x

instance (Num n, MySumType n r) => MySumType n (n->r) where
    mySum x = mySum . (x +)

Однако это работает только с такими звонками, как mySum (1::Int) (3::Int) (2::Int) :: Int, Без спецификатора типа в аргументах я получаю эту ошибку:

Нет экземпляра для (MySumType n0 Float), возникающего из-за использования mySum

Возможное исправление: добавьте объявление экземпляра для (MySumType n0 Float)

В выражении: mySum 1.0:: Float

В уравнении для `it': it = mySum 1.0:: Float

Что вызывает эту проблему и как я могу это исправить? Я подозреваю, что это как-то связано с типом целочисленных литералов, являющихся Num a => a, Также есть ли эквивалентная функция вышеупомянутой, которая не зависит от расширений? Используемые выше - это несколько классов типов параметров и гибкие экземпляры.

1 ответ

Решение

Я до сих пор не встречал убедительного варианта использования поливариадных функций в haskell, который пока не может быть решен с помощью списка или подобной структуры данных. Так что, если вы думаете, что у вас есть что-то новое, по той причине, по которой я играл с ними, я бы с удовольствием узнал, что это такое. Ниже приведено достаточно примеров, о некоторых из которых я должен был подумать, комментируя, что я отказался от своего заявления.

{-# language MultiParamTypeClasses #-}
{-# language FlexibleInstances #-}
{-# language TypeFamilies #-}
{-# language IncoherentInstances #-}

class (Num n) => MySumType n r where
    mySum :: n -> r

instance (Num n, m~n) => MySumType n m where
    mySum x = x

instance (Num n, MySumType n r, n~m) => MySumType n (m->r) where
    mySum x = mySum . (x +)

Затем после загрузки файла в ghci:

> mySum 1 2 4 5 6 7 :: Int
25
> mySum 1.1 2 4.6 5 6.9 7 :: Double
26.6

Вывод типа также может быть вашим другом в некоторых случаях, позволяя вам отбрасывать окончательную аннотацию типа, как в следующем надуманном случае:

> replicate (mySum 1 2 3 4) 6
[6,6,6,6,6,6,6,6,6,6]

Что касается:

Также есть ли эквивалентная функция вышеупомянутой, которая не зависит от расширений?

Я думаю, что вам не повезло. Я хотел бы отметить, что если у вас нет причин уходить из GHC или оставаться на Haskell98 или Haskell2010, расширения не причинят вам вреда и не вызовут особых проблем с совместимостью, так как большинство людей в любом случае, похоже, находятся на GHC.

Изменить: Дополнительное объяснение

Давайте начнем с объяснения различия между более простыми экземплярами. Я добавлю постфикс 2 к некоторым именам для реализации, которую я предоставил.

instance (Num n) => MySumType n n where
    mySum x = x

Если вы объедините это с объявлением класса:

class (Num n) => MySumType n r where
    mySum :: n -> r

mySum имеет тип подписи mySum :: (Num n) => n -> n, Тот n -> n говорит, что 1 функция арности, которая принимает тип n, производит n а также n имеет класс Num.

При использовании этого mySum Я должен указать, что я даю и что он производит.

mySum 1 :: Int
mySum (1 :: Int)

оба будут выдавать ошибки только при указании типа ввода и вывода, это даст результат:

mySum (1 :: Int) :: Int
            ^       ^
            |       specify output type
            specify input type

дает результат ( 1 в этом случае). Это потому, что вы дали экземпляр для n -> n но кто-то может позже добавить экземпляр для n -> m такие как Int -> Double как следующее:

instance MySumType Int Double where
    mySum x = 2 * fromIntegral x

> mySum (1::Int) :: Int
1
> mySum (1::Int) :: Double
2.0

Каждый из этих экземпляров соответствует очень узкой полосе всех возможных типов функций арности.

Теперь давайте посмотрим на модифицированную версию

instance (Num n, m~n) => MySumType n m where
    mySum x = x

mySum здесь есть тип подписи mySum :: (Num n, m~n) => n -> m этот тип подписи говорит для всех 1 функции арности, которые принимают тип n и тип продукции m где n имеет класс Num и m равно n, Обратите внимание, что это началось с соответствия всем 1 функциям арности, n -> m, любой n любому m затем положите на него контранты.

Теперь можно сделать:

> mySum2 (1::Int)
1
> mySum2 1 :: Int
1

Как только вход указан, выход также указан. Другие объяснения 1, 2.

Это не мешает нам указывать более конкретные случаи, такие как Int -> Double как и раньше:

instance MySumType Int Double where
    mySum x = 2 * fromIntegral x

> mySum2 1 :: Int
1
> mySum2 1 :: Double
1.0
> mySum2 (1 :: Int) :: Double
2.0

Только последний mySum2 работает от Int -> Double пример. Это свойство IncoherentInstances и я думаю, что я оставлю это на другой вопрос stackru, чтобы ответить на роль, IncoherentInstances играет.

Другие вопросы по тегам