Допустим, у меня есть следующая подпись типа:
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, верно? В моей голове реализация действительно соответствует подписи. Что насчет моей реализации нарушает ожидания этой подписи?
Полагаю, я неправильно понимаю значение слов «я могу вернуть любой b».
Вам также может понравиться этот вопрос; там я описываю типы как своего рода игру для двух игроков, и я думаю, что это хороший способ резюмировать то, что здесь происходит.





Нет, ваша подпись типа, которая на самом деле
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». Это не так. :)
«Это не ваша функция решает, какой тип возвращать. Это определяет использование вашей функции». - это утверждение очень помогает. Спасибо. Иногда сложно разобраться в параметрическом полиморфизме.
@CameronBall Я думаю, это очень помогает визуализировать аргументы неявного типа. Например, когда мы пишем id True, GHC внутренне расширяет это до id @Bool True, где часть @Bool выбирает тип a в id :: forall a. a->a. По сути, полиморфные функции - это функции с одним или несколькими аргументами типа, которые необходимо передать с помощью специального синтаксиса @T или опустить, чтобы вывод типа мог заполнить их за нас. В качестве упражнения вы можете попробовать добавить все @T самостоятельно и посмотреть, что находится под капотом (сначала включите расширение TypeApplications).
Потому что ваша подпись говорит: «Я могу вернуть любой
b, если этоEq b), но ваша реализация явно возвращает толькоInt.