отразить все эти зависимости в целочисленных типах:
data Matn m a = ...
instance Numa => AdditiveGroup( Matn m a) where
a ^+^b
= ...
zeroV
= ...
negateV a
= ...
mul :: Numa => Matn m a -> Matm k a -> Matn k a
При таких определениях мы не сможем сложить матрицы разных размеров. Причём ошибка будет вычис-
лена до выполнения программы. Это освобождает от проверки границ внутри алгоритма умножения матриц.
Если алгоритм запустился, то мы знаем, что размеры аргументов соответствуют.
Скоро в ghc появится поддержка чисел на уровне типов. Это будет специальное расширение
TypeLevelNats, при включении которого можно будет пользоваться численными литералами в типах,
также будут определены операции-семейства типов на численных типах с привычными именами +, *.
Классы с несколькими типами
Рассмотрим несколько полезных расширений, относящихся к определению классов и экземпляров клас-
сов. Расширение MultiParamTypeClassesпозволяет объявлять классы с несколькими аргументами. Например
взгляните на такой класс:
class Isoa b where
to
::a ->b
from
::b ->a
Так мы можем определить изоморфизм между типами a и b
260 | Глава 17: Дополнительные возможности
Экземпляры классов для синонимов
Расширение TypeSynonymInstancesпозволяет определять экземпляры для синонимов типов. Мы уже
пользовались этим расширением, когда определяли рекурсивные типы через тип Fix, там нам нужно бы-
ло определить экземпляр Numдля синонима Nat:
type Nat = Fix N
instance Num Nat where
В рамках стандарта все суперклассы должны быть простыми. Все они имеют вид Ta. Если мы хотим хотим
использовать суперклассы с составными типами, нам придётся подключить расширение FlexibleContexts.
Этим расширением мы пользовались, когда определяли экземпляр Showдля Fix:
instance Show(f ( Fixf)) => Show( Fixf) where
show x =”(” ++show (unFix x) ++”)”
Функциональные зависимости
Класс можно представить как множество типов, для которых определены данные операции. С появлением
расширения MultiParamTypeClassesмы можем определять операции класса для нескольких типов. Так наше
множество классов превращается в отношение. Наш класс связывает несколько типов между собой. Если из
одной компоненты отношения однозначно следует другая, такое отношение принято называть функцией.
Например обычную функцию одного аргумента можно представить как множество пар (x, f x). Для того
чтобы множество таких пар было функцией необходимо, чтобы выполнялось свойство:
forall x, y .
x ==y =>f x ==f y
Для одинаковых входов мы получаем одинаковые выходы. С функциональными зависимостями мы мо-
жем ввести такое ограничение на классы с несколькими аргументами. Рассмотрим практический пример.
Библиотека Booleanопределяет обобщённые логические значения,
class Booleanb where
true, false ::b
notB
::b ->b
( &&*), ( ||*) ::b ->b ->b
Логические значения определены в терминах простейших операций, теперь мы можем обобщить связку
if-then-elseи классы Eqи Ord:
class Booleanbool => IfBbool a |a ->bool where
ifB ::bool ->a ->a ->a
class Booleanbool => EqBbool a |a ->bool where
( ==*), ( /=*) ::a ->a ->bool
class Booleanbool => OrdBbool a |a ->bool where
( <*), ( >=*), ( >*), ( <=*) ::a ->a ->bool
Каждый из классов определён на двух типах. Один из них играет роль обычных логических значений, а
второй тип~– это такой же параметр как и в обычных классах из модуля Prelude. В этих определениях нам
встретилась новая конструкция: за переменными класса через разделитель “или” следует что-то похожее на
тип функции. В этом типе мы говорим, что из типа a следует тип bool, или тип a однозначно определяет тип
bool. Эта информация помогает компилятору выводить типы. Если он встретит в тексте выражение v =a <*
Читать дальше