Вот мы и добрались до Второго Кита Haskell — до Типов. Конечно, мы работали с типами почти с самого начала, но вам уже порядком надоели все эти Int и String, не правда ли? Пришла пора познакомиться с типами куда ближе.
Удивительно, но в Haskell очень мало встроенных типов, то есть таких, о которых компилятор знает с самого начала. Есть Int, есть Double, Char, ну и ещё несколько. Все же остальные типы, даже носящие статус стандартных, не являются встроенными в язык. Вместо этого они определены в стандартной или иных библиотеках, причём определены точно так же, как мы будем определять и наши собственные типы. А поскольку без своих типов написать сколь-нибудь серьёзное приложение у нас не получится, тема эта достойна самого пристального взгляда.
Определим тип Transport для двух известных протоколов транспортного уровня модели OSI:
data Transport = TCP | UDPПеред нами — очень простой, но уже наш собственный тип. Рассмотрим его внимательнее.
Ключевое слово data — это начало определения типа. Далее следует название типа, в данном случае Transport. Имя любого типа обязано начинаться с большой буквы. Затем идёт знак равенства, после которого начинается фактическое описание типа, его «тело». В данном случае оно состоит из двух простейших конструкторов. Конструктор значения (англ. data constructor) — это то, что строит значение данного типа. Здесь у нас два конструктора, TCP и UDP, каждый из которых строит значение типа Transport. Имя конструктора тоже обязано начинаться с большой буквы. Иногда для краткости конструктор значения называют просто конструктором.
Подобное определение легко читается:
data Transport = TCP | UDP
тип Transport это TCP или UDPТеперь мы можем использовать тип Transport, то есть создавать значения этого типа и что-то с ними делать. Например, в let-выражении:
let protocol = TCPМы создали значение protocol типа Transport, использовав конструктор TCP. А можно и так:
let protocol = UDPХотя мы использовали разные конструкторы, тип значения protocol в обоих случаях один и тот же — Transport.
Расширить подобный тип предельно просто. Добавим новый протокол SCTP (Stream Control Transmission Protocol):
data Transport = TCP | UDP | SCTPТретий конструктор значения дал нам третий способ создать значение типа Transport.
Задумаемся: говоря о значении типа Transport — о чём в действительности идёт речь? Казалось бы, значения-то фактического нет: ни числа никакого, ни строки — просто три конструктора. Так вот они и есть значения. Когда мы пишем:
let protocol = SCTPмы создаём значение типа Transport с конкретным содержимым в виде SCTP. Конструктор — это и есть содержимое. Данный вид конструктора называется нульарным (англ. nullary). Тип Transport имеет три нульарных конструктора. И даже столь простой тип уже может быть полезен нам:
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В результате увидим:
That's TCP protocol.Функция checkProtocol объявлена как принимающая аргумент типа Transport, а применяется она к значению, порождённому конструктором TCP. В данном случае конструкция case-of сравнивает аргумент с конструкторами. Именно поэтому нам не нужна функция otherwise, ведь никаким иным способом, кроме как с помощью трёх конструкторов, значение типа Transport создать невозможно, а значит, один из конструкторов гарантированно совпадёт.
Тип, состоящий только из нульарных конструкторов, называют ещё перечислением (англ. enumeration). Конструкторов может быть сколько угодно, в том числе один-единственный (хотя польза от подобного типа была бы невелика). Вот ещё один известный пример:
data Day = Sunday
| Monday
| Tuesday
| Wednesday
| Thursday
| Friday
| SaturdayОбратите внимание на форматирование, когда ментальные «ИЛИ» выровнены строго под знаком равенства. Такой стиль вы встретите во многих реальных Haskell-проектах.
Значение типа Day отражено одним из семи конструкторов. Сделаем же с ними что-нибудь:
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, но об этом узнаем в одной из следующих глав.
А теперь мы можем познакомиться с типами куда более полезными.