haskell-notes

текст 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

Страницы: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162