52700.fb2
class Boolean bool => IfB bool a | a -> bool where
ifB :: bool -> a -> a -> a
class Boolean bool => EqB bool a | a -> bool where
(==*), (/=*) :: a -> a -> bool
class Boolean bool => OrdB bool a | a -> bool where
(<*), (>=*), (>*), (<=*) :: a -> a -> bool
Каждый из классов определён на двух типах. Один из них играет роль обычных логических значений, а
второй тип~– это такой же параметр как и в обычных классах из модуля Prelude. В этих определениях нам
встретилась новая конструкция: за переменными класса через разделитель “или” следует что-то похожее на
тип функции. В этом типе мы говорим, что из типа a следует тип bool, или тип a однозначно определяет тип
bool. Эта информация помогает компилятору выводить типы. Если он встретит в тексте выражение v = a <*
b и тип одного из аргументов a или b известен, то тип v будет определён по зависимости.
Зачем нам может понадобиться такая система классов? Например, с ней мы можем определить экземпляр
Boolean для предикатов или функций вида a -> Bool и затем определить три остальных класса для функций
вида a -> b. Мы сравниваем не отдельные логические значения, а функции которые возвращают логические
значения. Так в выражении ifB c t e функция c играет роль “маски”, если на данном значении функция c
вернт истину, то мы воспользуемся значением функции t, иначе возьмём результат из функции e. Например
так мы можем определить функцию модуля:
*Boolean> let absolute = ifB (> 0) id negate
*Boolean> map absolute [-10 .. 10]
[10,9,8,7,6,5,4,3,2,1,0,1,2,3,4,5,6,7,8,9,10]
Расширения | 261
Мы можем указать несколько зависимостей (через запятую) или зависимость от нескольких типов (через
пробел, слева от стрелки):
class C a b c | a -> b, b c -> a where
...
Отметим, что многие функциональные зависимости можно выразить через семейства типов. Пример из
библиотеки Boolean можно было бы записать так:
class Boolean a where
true, false
:: a
(&&*), (||*)
:: a -> a -> a
class Boolean (B a) => IfB a where
type B a :: *
ifB :: (B a) -> a -> a -> a
class IfB a => EqB a where
(==*), (/=*) :: a -> a -> B a
class IfB a => OrdB a where
(<*), (>*), (>=*), (<=*) :: a -> a -> B a
Исторически первыми в Haskell появились функциональные зависимости. Поэтому некоторые пакеты на
Hackage определены в разных вариантах. Семейства типов используются более охотно.
Ограничение мономорфизма
В Haskell мы можем не писать типы функций. Они будут выведены компилятором автоматически. Но
написание типов функций считается признаком хорошего стиля. Поскольку по типам можно догадаться чем
функция занимается. Но есть в правиле вывода типов одно исключение. Если мы напишем:
f = show
То компилятор сообщит нам об ошибке. Это выражение приводит к ошибке, которая вызвана ограничени-
ем мономорфизма. Мы говорили о нём в главе о типах. Часто в сильно обобщённых библиотеках, с больши-