haskell-notes

говорим о значениях (не)допустимых для некоторого типа, например значение True допустимо для Bool, но

недопустимо для Nat.

Сами типы строятся не произвольным образом. Мы узнали, что при их построении используются две ос-

новные операции, это сумма и произведение типов. Это говорит о том, что в типах должны быть какие-то

закономерности, которые распространяются на все значения. В этой главе мы посмотрим на эти закономер-

ности.

3.1 Структура алгебраических типов данных

Итак у нас лишь две операции: сумма и произведение. Давайте для начала рассмотрим два крайних

случая.

• Только произведение типов

data T = Name T1 T2 … TN

Мы говорим, что значение нашего нового типа T состоит из значений типов T1, T2, … , TN и у нас есть

лишь один способ составить значение этого типа. Единственное, что мы можем сделать это применить

к значениям типов Ti конструктор Name.

Пример:

data Time = Time Hour Second Minute

• Только сумма типов

data T = Name1 | Name2 | … | NameN

40 | Глава 3: Типы

Мы говорим, что у нашего нового типа T может быть лишь несколько значений, и перечисляем их в

альтернативах через знак |.

Пример:

data Bool = True | False

Сделаем первое наблюдение: каждое произведение типов определяет новый конструктор. Число кон-

структоров в типе равно числу альтернатив. Так в первом случае у нас была одна альтернатива и следова-

тельно у нас был лишь один конструктор Name.

Имена конструкторов должны быть уникальными в пределах модуля. У нас нет таких двух типов, у ко-

торых совпадают конструкторы. Это говорит о том, что по имени конструктора компилятор знает значение

какого типа он может построить.

Произведение типов состоит из конструктора, за которым через пробел идут подтипы. Такая структура

не случайна, она копирует структуру функции. В качестве имени функции выступает конструктор, а в ка-

честве аргументов – значения заданных в произведении подтипов. Функция-конструктор после применения

“оборачивает” значения аргументов и создаёт новое значение. За счёт этого мы могли бы определить типы

по-другому. Мы могли бы определить их в стиле классов типов:

data Bool where

True

:: Bool

False :: Bool

Мы видим “класс” Bool, у которого два метода. Или определим в таком стиле Nat:

data Nat where

Zero

:: Nat

Succ

:: Nat -> Nat

Мы переписываем подтипы по порядку в аргументы метода. Или определим в таком стиле списки:

data [a] where

[]

:: [a]

(:)

:: a -> [a] -> [a]

Конструктор пустого списка [] является константой, а конструктор объединения элемента со списком

(:), является функцией. Когда я говорил, что типы определяют примитивы и методы составления из прими-

тивов, я имел ввиду, что некоторые конструкторы по сути являются константами, а другие функциями.

Эти “методы” определяют базовые значения типа, все другие значения будут комбинациями базовых.

При этом сумма типов, определяет число методов “классе” типа, то есть число базовых значений, а произ-

ведение типов в каждой альтернативе определяет имя метода (именем конструктора) и состав аргументов

(перечислением подтипов).

3.2 Структура констант

Мы уже знаем, что значения могут быть функциями и константами. Объявляя константу, мы даём имя-

синоним некоторой комбинации базовых конструкторов. В функции мы говорим как по одним значениям

получить другие. В этом и следующем разделе мы посмотрим на то, как типы определяют структуру констант

и функций.

Давайте присмотримся к константам:

Succ (Succ Zero)

Neg (Add One (Mul Six Ten))

Not (Follows A (And A B))

Cons 1 (Cons 2 (Cons 3 (Cons 4 Nil)))

Заменим все функциональные конструкторы на букву f (от слова function), а все примитивные конструк-

торы на букву c (от слова constant).

f (f c)

f (f c (f c c))

f (f c (f c c))

f c (f c (f c (f c c)))

Те кто знаком с теорией графов, возможно уже узнали в этой записи строчную запись дерева. Все зна-

чения в Haskell являются деревьями. Узел дерева содержит составной конструктор, а лист дерева содержит

примитивный конструктор. Далее будет небольшой подраздел посвящённый терминологии теории графов,

которая нам понадобится, будет много картинок, если вам это известно, то вы можете спокойно его пропу-

стить.

Структура констант | 41

Несколько слов о теории графов

Если вы не знакомы с теорией графов, то сейчас как раз самое время с ней познакомится, хотя бы на

уровне основных терминов. Теория графов изучает дискретные объекты в терминах зависимостей между

объектами или связей. При этом объекты и связи можно изобразить графически.

Граф состоит из узлов и рёбер, которые соединяют узлы. Приведём пример графа:

8

7

c

f

6

a

b

d

e

5

1

2

g

h

3

4

Рис. 3.1: Граф

В этом графе восемь узлов, они пронумерованы, и восемь рёбер, они обозначены буквами. Теорию графов

придумал Леонард Эйлер, когда решал задачу о кёнингсбергских мостах. Он решал задачу о том, можно ли

обойти все семь кёнингсбергских мостов так, чтобы пройти по каждому лишь один раз. Эйлер представил

мосты в виде рёбер а участки суши в виде узлов графа и показал, что это сделать нельзя. Но мы отвлеклись.

А что такое дерево? Дерево это такой связанный граф, у которого нет циклов. Несвязанный граф образует

несколько островков, или множеств узлов, которые не соединены рёбрами. Циклы – это замкнутые последо-

Страницы: 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