Ответ 1
Hm. Давайте забудем о типах на мгновение.
Скажем, у вас есть две функции:
import qualified Data.IntMap as IM
a :: Int -> Float
a x = fromInteger (x * x) / 2
l :: Int -> String
l x = fromMaybe "" $ IM.lookup x im
where im = IM.fromList -- etc...
Скажите, что существует какое-то значение n :: Int
, о котором вы заботитесь. Учитывая только значение a n
, как вы находите значение l n
? Разумеется, вы этого не сделаете.
Как это уместно? Ну, тип myLength
равен A a -> Int
, где A a
является результатом применения "функции типа" A
к некоторому типу A
. Тем не менее, myLength
является частью класса типа, параметр class A
используется для выбора реализации myLength
для использования. Итак, учитывая значение какого-либо определенного типа B
, применение myLength
к нему дает тип B -> Int
, где B ~ A a
, и вам нужно знать A
, чтобы найти реализацию myLength
. Учитывая только значение A a
, как вы находите значение A
? Конечно, вы этого не делаете.
Вы можете обоснованно возразить, что в вашем коде функция A
обратима, в отличие от функции A
в моем предыдущем примере. Это верно, но компилятор ничего не может с этим сделать из-за предположения открытого мира, где задействованы классы типов; теоретически, ваш модуль может быть импортирован другим модулем, который определяет свой собственный экземпляр, например:
instance C Bool where
type A Bool = [String]
Глупый? Да. Действительный код? Также да.
Во многих случаях использование конструкторов в Haskell служит для создания тривиально-инъективных функций: конструктор вводит новую сущность, которая определяется только и уникально аргументами, которые она дала, что упрощает восстановление исходных значений. Это как раз разница между двумя версиями вашего кода; семейство данных делает функцию типа обратимой, определяя новый, различный тип для каждого аргумента.