Почему эта реализация недействительна?

Допустим, у меня есть следующая подпись типа:

someFunction :: (Eq a, Eq b) => a -> b

С реализацией:

someFunction x = (2 :: Int)

(Не заглядывайте слишком далеко, это просто пример).

Мое понимание подписи заключается в том, что «someFunction принимает аргумент, который является экземпляром класса типов Eq, и возвращает значение (которое может быть другого типа), которое является экземпляром класса типов Eq». Int является экземпляром Eq, так почему GHC расстраивается из-за этой реализации?

Ошибка делает это достаточно очевидным:

Couldn't match expected type ‘b’ with actual type ‘Int’
     ‘b’ is a rigid type variable bound by
       the type signature for:
         someFunction :: forall a b. (Eq a, Eq b) => a -> b

Я думаю, что я не понимаю требования, чтобы это работало "для всех" b. Любой код, использующий эту функцию, должен полагаться только на тот факт, что b является экземпляром Eq, верно? В моей голове реализация соответствует подписи. Что насчет моей реализации нарушает ожидания этой подписи?


person Cameron Ball    schedule 22.10.2018    source источник
comment
Поскольку ваша подпись говорит, что я могу вернуть любой b, если это Eq b), но ваша реализация явно возвращает только Ints.   -  person Willem Van Onsem    schedule 22.10.2018
comment
Я полагаю, я неправильно понимаю значение того, что я могу вернуть любое b.   -  person Cameron Ball    schedule 22.10.2018
comment
Вам также может понравиться этот вопрос; там я описываю типы как своего рода игру для двух игроков, и я думаю, что это хороший способ обобщить то, что здесь происходит.   -  person Daniel Wagner    schedule 22.10.2018


Ответы (1)


Нет, ваша типовая подпись, которая на самом деле

forall a b. (Eq a, Eq b) => a -> b

означает, что ваша функция должна вызываться с любыми типами a и b, как определено сайтом вызова, при условии, что оба являются экземплярами Eq.

Не ваша функция решает, какой тип возвращать. Это определяет использование вашей функции.

Таким образом, вы должны быть в состоянии написать

    let { i :: Int; i = 1;
          n :: Integer; y :: Double;
          n = foo i;   -- foo :: Int -> Integer
          y = foo i    -- foo :: Int -> Double
        }

и, как вы можете видеть, единственная реализация для вашей функции — никакая реализация:

foo _ = x where {x = x}

потому что у вас нет возможности создать значение любого типа, которое от вас требуется. Этот тип может быть чем угодно, и у вас нет возможности что-либо узнать об этом.


Между прочим, другие классы типов могут фактически позволить вам определить что-то здесь, например

foo :: (Enum a, Enum b, Bounded a, Bounded b) => a -> b 
foo a = snd . last $ zip [minBound .. a] (cycle [minBound ..])

Я не говорю, что это разумное определение, просто это возможно:

> foo () :: Bool
False

> foo True :: Int
-9223372036854775807

> foo (0 :: Int) :: Bool
Interrupted.

Вероятно, это распространенное заблуждение для программистов, пришедших из более обычных языков, думать, что foo :: (Eq a) => a означает «Я могу определить foo для возврата любого типа, который я хочу, пока он находится в Eq». Это не так. :)

person Will Ness    schedule 22.10.2018
comment
Не ваша функция решает, какой тип возвращать. Это определяет использование вашей функции. - это утверждение очень помогает. Спасибо. Иногда сложно понять параметрический полиморфизм. - person Cameron Ball; 22.10.2018
comment
@CameronBall Я думаю, что это очень помогает визуализировать аргументы неявного типа. Например, когда мы пишем id True, GHC внутренне расширяет это до id @Bool True, где часть @Bool выбирает тип a в id :: forall a. a->a. По сути, полиморфные функции — это функции с одним или несколькими аргументами типа, которые необходимо передавать со специальным синтаксисом @T или опускать, чтобы вывод типа мог заполнить их для нас. В качестве упражнения можно попробовать самостоятельно добавить все @T и посмотреть, что там под капотом (сначала включить расширение TypeApplications). - person chi; 22.10.2018