Вот мы и добрались до Второго Кита 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
, но об этом узнаем в одной из следующих глав.
А теперь мы можем познакомиться с типами куда более полезными.