b и тип одного из аргументов a или b известен, то тип v будет определён по зависимости.
Зачем нам может понадобиться такая система классов? Например, с ней мы можем определить экземпляр
Booleanдля предикатов или функций вида a -> Boolи затем определить три остальных класса для функций
вида a ->b. Мы сравниваем не отдельные логические значения, а функции которые возвращают логические
значения. Так в выражении ifB c t e функция c играет роль “маски”, если на данном значении функция c
вернт истину, то мы воспользуемся значением функции t, иначе возьмём результат из функции e. Например
так мы можем определить функцию модуля:
*Boolean> letabsolute =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 Ca b c |a ->b, b c ->a where
...
Отметим, что многие функциональные зависимости можно выразить через семейства типов. Пример из
библиотеки Booleanможно было бы записать так:
class Booleana where
true, false
::a
( &&*), ( ||*)
::a ->a ->a
class Boolean( Ba) => IfBa where
type Ba :: *
ifB ::( Ba) ->a ->a ->a
class IfBa => EqBa where
( ==*), ( /=*) ::a ->a -> Ba
class IfBa => OrdBa where
( <*), ( >*), ( >=*), ( <=*) ::a ->a -> Ba
Исторически первыми в Haskell появились функциональные зависимости. Поэтому некоторые пакеты на
Hackageопределены в разных вариантах. Семейства типов используются более охотно.
Ограничение мономорфизма
В Haskell мы можем не писать типы функций. Они будут выведены компилятором автоматически. Но
написание типов функций считается признаком хорошего стиля. Поскольку по типам можно догадаться чем
функция занимается. Но есть в правиле вывода типов одно исключение. Если мы напишем:
f =show
То компилятор сообщит нам об ошибке. Это выражение приводит к ошибке, которая вызвана ограничени-
ем мономорфизма. Мы говорили о нём в главе о типах. Часто в сильно обобщённых библиотеках, с больши-
ми зависимостями в типах выписывать типы крайне неудобно. Например в библиотеке создания парсеров
Parsec. С этим ограничением приходится писать огромные объявления типов для крохотных выражений.
Что-то вроде:
fun ::( Streams m t, Showt) => ParsecTs u m a -> ParsecTs u m [a]
fun =g .h (q x) y
И так для любого выражения. В этом случае лучше просто выключить ограничение, добавив в начало
файла:
{-# Language NoMonomorphismRestriction #-}
Полиморфизм высших порядков
Когда мы говорили об STнам встретилась функция с необычным типом:
runST ::(forall s . STs a) ->a
Слово forall обозначает для любых. Любой полиморфный тип в Haskell подразумевает, что он определён
для любых типов. Например, когда мы пишем:
reverse ::[a] ->[a]
map
::(a ->b) ->[a] ->[b]
На самом деле мы пишем:
reverse ::forall a .[a] ->[a]
map
::forall a b .(a ->b) ->[a] ->[b]
262 | Глава 17: Дополнительные возможности
По названию слова forall может показаться, что оно несёт в себе много свободы. Оно говорит о том, что
функция определена для любых типов. Но если присмотреться, то эта свобода оказывается жёстким огра-
ничением. “Для любых” означает, что мы не можем делать никаких предположений о внутренней природе
значения. Мы не можем разбирать такие значения на составляющие части. Мы можем только подставлять
их в новые полиморфные функции (как в map), отбрасывать (как const) или перекладывать из одного ме-
ста в другое (как в swap или reverse). Мы можем немного смягчить ограничение, если укажем в контексте
функции какие классы определены для значений данного типа.
Все стандартные полиморфные типы имеют вид:
fun ::forall a b ..z . Expr(a, b, ..., z)
Причём Exprне содержит forall, а только стрелки и применение новых типов к параметрам. Такой тип
Читать дальше