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
, являющийся частью класса типов, параметр класса a
используется, чтобы выбрать, какую реализацию myLength
использовать. Итак, учитывая значение некоторого определенного типа B
, применение к нему myLength
дает тип B -> Int
, где B ~ A a
, и вам нужно знать a
, чтобы посмотреть до реализации myLength
. Учитывая только значение A a
, как найти значение a
? Конечно, нет.
Вы могли бы обоснованно возразить, что в вашем коде функция A
является обратимой, в отличие от функции a
в моем предыдущем примере. Это правда, но компилятор не может ничего с этим поделать из-за предположения open world , где задействованы классы типов; ваш модуль теоретически может быть импортирован другим модулем, который определяет свой собственный экземпляр, например ::
instance C Bool where
type A Bool = [String]
Глупый? Да. Действительный код? Тоже да.
Во многих случаях использование конструкторов в Haskell служит для создания тривиально инъективных функций : конструктор вводит новую сущность, которая определяется только и уникально с помощью заданных аргументов, упрощая восстановление исходные значения. В этом и заключается разница между двумя версиями вашего кода; семейство данных делает функцию типа обратимой, определяя новый, отдельный тип для каждого аргумента.