текст Group a => . А во второй функции isE мы воспользовались методом e из класса Group и методом (==)
из класса Eq, поэтому функция имеет контекст (Group a, Eq a) => .
Контекст классов типов. Суперклассы
Класс типов также может содержать контекст. Он указывается между словом class и именем класса.
Например
class IsPerson a
class IsPerson a => HasName a where
name :: a -> String
Это определение говорит о том, что мы можем сделать экземпляр класса HasName только для тех типов,
которые содержатся в IsPerson. Мы говорим, что класс HasName содержится в IsPerson. В этом случае класс
из контекста IsPerson называют суперклассом для данного класса HasName.
Это сказывается на контексте объявления типа. Теперь, если мы пишем
Классы типов | 19
fun :: HasName a => a -> a
Это означает, что мы можем пользоваться для значений типа a как методами из класса HasName, так и
методами из класса IsPerson. Поскольку если тип принадлежит классу HasName, то он также принадлежит и
IsPerson.
Запись (IsPerson a => HasName a) немного обманывает, было бы точнее писать IsPerson a <= HasName
a, если тип a в классе HasName, то он точно в классе IsPerson, но в Haskell закрепилась другая запись.
1.5 Экземпляры классов типов
В экземплярах (instance) классов типов мы даём конкретное наполнение для методов класса типов. Опре-
деление экземпляра пишется так же, как и определение класса типа, но вместо class мы пишем instance,
вместо некоторого типа наш конкретный тип, а вместо типов методов – уравнения для них.
Определим экземпляры для Bool
Класс Eq:
instance Eq Bool where
(==) True
True
= True
(==) False False = True
(==) _
_
= False
(/=) a b
= not (a == b)
Класс Show:
instance Show Bool where
show True
= ”True”
show False = ”False”
Класс Group:
instance Group Bool where
e
= True
(+) a b = and a b
inv a
= not a
Отметим важность наличия свойств (ограничений) у значений, определённых в классе типов. Так, на-
пример, в классе типов “сравнение на равенство” для любых двух значений данного типа одна из операций
должна вернуть “истину”, а другая “ложь”, то еесть два элемента данного типа либо равны, либо не рав-
ны. Недостаточно определить равенство для конкретного типа, необходимо убедиться в том, что для всех
элементов данного типа свойства понятия равенства не нарушаются.
На самом деле приведённое выше определение экземпляра для Group не верно, хотя по типам оно под-
ходит. Оно не верно как раз из-за нарушения свойств. Для группы необходимо, чтобы для любого a выпол-
нялось:
inv a + a == e
У нас лишь два значения, и это свойство не выполняется ни для одного из них. Проверим:
inv True
+ True
=> (not True) + True
=> False
+ True
=> and False
True
=> False
inv False
+ False
=> (not False) + False
=> True
+ False
=> and True
False
=> False
Проверять свойства очень важно, потому что другие люди, читая ваш код и используя ваши функции,
будут на них рассчитывать.
20 | Глава 1: Основы
1.6 Ядро Haskell
Фуууухх. Мы закончили наш пробег. Теперь можно остановиться, отдышаться и подвести итоги. Давайте
вспомним синтаксические конструкции, которые нам встретились.
Модули
module New(edef1, edef2, … , edefN) where
import Old1(idef11, idef12, … , idef1N)
import Old2(idef21, idef22, … , idef2M)
…
import OldK(idefK1, idefK2, … , idefKP)
— определения :
…
Ключевые слова: module, where, import. Мы определили модуль с именем New, который экспортирует
определения edef1, edef2, … , edefN. И импортирует определения из модулей Old1, Old2, и т.д., определения
написаны в скобках за ключевыми словами import и именами модулей.
Типы
Тип определяется с помощью:
• Перечисления альтернатив через |
data Type = Alt1 | Alt2 | … | AltN
Эту операцию называют суммой типов.
• Составления сложного типа из подтипов, пишем конструктор первым, затем через пробел подтипы:
data Type = Name
Sub1
Sub2
…
SubN
Эту операцию называют произведением типов.
Есть одно исключение: если тип состоит из двух подтипов, мы можем дать конструктору символьное
(а не буквенное) имя, но оно должно начинаться с двоеточия :, как в случае списка, например, можно
делать такие определения типов:
data Type = Sub1 :+ Sub2
data Type = Sub1 😐 Sub2
• Комбинации суммы и произведения типов:
data Type = Name1
Sub11
Sub12
…
Sub1N
| Name2
Sub21
Sub22
…
Sub2M
…
| NameK
SubK1
SubK2
…
SubKP
Такие типы называют алгебраическими типами данных. С помощью типов мы определяем основные поня-
тия и способы их комбинирования.
Значения
Как это ни странно, нам встретилась лишь одна операция создания значений: определение синонима. Она
пишется так
name x1
x2 … xN = Expr1
name x1
x2 … xN = Expr2
name x1
x2 … xN = Expr3
Слева от знака равно стоит составное имя, а справа от знака равно некоторое выражение, построенное
согласно типам. Разные комбинации имени name с параметрами определяют разные уравнения для синонима
name.
Также мы видели символ _, который означает “всё, что угодно” на месте аргумента. А также мы увидели,
как с помощью переменных можно перетаскивать значения из аргументов в результат.
Ядро Haskell | 21
Классы типов
Нам встретилась одна конструкция определения классов типов:
class Name a where