chapters/19-own-types.md
# Наши типы
Вот мы и добрались до Второго Кита Haskell — до **Типов**. Конечно, мы работали с типами почти с самого начала, но вам уже порядком надоели все эти `Int` и `String`, не правда ли? Пришла пора познакомиться с типами куда ближе.
## Знакомство
Удивительно, но в Haskell очень мало встроенных типов, то есть таких, о которых компилятор знает с самого начала. Есть `Int`, есть `Double`, `Char`, ну и ещё несколько. Все же остальные типы, даже носящие статус стандартных, не являются встроенными в язык. Вместо этого они определены в стандартной или иных библиотеках, причём определены точно так же, как мы будем определять и наши собственные типы. А поскольку без своих типов написать сколь-нибудь серьёзное приложение у нас не получится, тема эта достойна самого пристального взгляда.
Определим тип `Transport` для двух известных протоколов транспортного уровня модели OSI:
```haskell
data Transport = TCP | UDP
```
Перед нами — очень простой, но уже наш собственный тип. Рассмотрим его внимательнее.
Ключевое слово `data` — это начало определения типа. Далее следует название типа, в данном случае `Transport`. Имя любого типа обязано начинаться с большой буквы. Затем идёт знак равенства, после которого начинается фактическое описание типа, его «тело». В данном случае оно состоит из двух простейших конструкторов. Конструктор значения (англ. data constructor) — это то, что строит значение данного типа. Здесь у нас два конструктора, `TCP` и `UDP`, каждый из которых строит значение типа `Transport`. Имя конструктора тоже обязано начинаться с большой буквы. Иногда для краткости конструктор значения называют просто конструктором.
Подобное определение легко читается:
```haskell
data Transport = TCP | UDP
тип Transport это TCP или UDP
```
Теперь мы можем использовать тип `Transport`, то есть создавать значения этого типа и что-то с ними делать. Например, в `let`-выражении:
```haskell
let protocol = TCP
```
Мы создали значение `protocol` типа `Transport`, использовав конструктор `TCP`. А можно и так:
```haskell
let protocol = UDP
```
Хотя мы использовали разные конструкторы, тип значения `protocol` в обоих случаях один и тот же — `Transport`.
Расширить подобный тип предельно просто. Добавим новый протокол SCTP (Stream Control Transmission Protocol):
```haskell
data Transport = TCP | UDP | SCTP
```
Третий конструктор значения дал нам третий способ создать значение типа `Transport`.
## Значение-пустышка
Задумаемся: говоря о значении типа `Transport` — о чём в действительности идёт речь? Казалось бы, значения-то фактического нет: ни числа никакого, ни строки — просто три конструктора. Так вот они и есть значения. Когда мы пишем:
```haskell
let protocol = SCTP
```
мы создаём значение типа `Transport` с конкретным содержимым в виде `SCTP`. Конструктор — это и есть содержимое. Данный вид конструктора называется нульарным (англ. nullary). Тип `Transport` имеет три нульарных конструктора. И даже столь простой тип уже может быть полезен нам:
```haskell
checkProtocol :: Transport -> String
checkProtocol transport = case transport of
TCP -> "That's TCP protocol."
UDP -> "That's UDP protocol."
SCTP -> "That's SCTP protocol."
main :: IO ()
main = putStrLn . checkProtocol $ TCP
```
В результате увидим:
```bash
That's TCP protocol.
```
Функция `checkProtocol` объявлена как принимающая аргумент типа `Transport`, а применяется она к значению, порождённому конструктором `TCP`. В данном случае конструкция `case-of` сравнивает аргумент с конструкторами. Именно поэтому нам не нужна функция `otherwise`, ведь никаким иным способом, кроме как с помощью трёх конструкторов, значение типа `Transport` создать невозможно, а значит, один из конструкторов гарантированно совпадёт.
Тип, состоящий только из нульарных конструкторов, называют ещё перечислением (англ. enumeration). Конструкторов может быть сколько угодно, в том числе один-единственный (хотя польза от подобного типа была бы невелика). Вот ещё один известный пример:
```haskell
data Day = Sunday
| Monday
| Tuesday
| Wednesday
| Thursday
| Friday
| Saturday
```
Обратите внимание на форматирование, когда ментальные «ИЛИ» выровнены строго под знаком равенства. Такой стиль вы встретите во многих реальных Haskell-проектах.
Значение типа `Day` отражено одним из семи конструкторов. Сделаем же с ними что-нибудь:
```haskell
data WorkMode = FiveDays | SixDays
workingDays :: WorkMode -> [Day]
workingDays FiveDays = [ Monday
, Tuesday
, Wednesday
, Thursday
, Friday
]
workingDays SixDays = [ Monday
, Tuesday
, Wednesday
, Thursday
, Friday
, Saturday
]
```
Функция `workingDays` возвращает список типа `[Day]`, и в случае пятидневной рабочей недели, отражённой конструктором `FiveDays`, этот список сформирован пятью конструкторами, а в случае шестидневной — шестью конструкторами.
Польза от типов, сформированных нульарными конструкторами, не очень велика, хотя встречаться с такими типами вы будете часто.
Приоткрою секрет: новый тип можно определить не только с помощью ключевого слова `data`, но об этом узнаем в одной из следующих глав.
А теперь мы можем познакомиться с типами куда более полезными.