ghci> show Wednesday
"Wednesday"
ghci> read "Saturday" :: Day
Saturday
Поскольку он имеет экземпляры классов Eq
и Ord
, допускаются сравнение и проверка на равенство:
ghci> Saturday == Sunday
False
ghci> Saturday == Saturday
True
ghci> Saturday > Friday
True
ghci> Monday `compare` Wednesday
LT
Наш тип также имеет экземпляр класса Bounded
, так что мы можем найти минимальный и максимальный день.
ghci> minBound :: Day
Monday
ghci> maxBound :: Day
Sunday
Благодаря тому что тип имеет экземпляр класса Enum
, можно получать предшествующие и следующие дни, а также задавать диапазоны дней.
ghci> succ Monday
Tuesday
ghci> pred Saturday
Friday
ghci> [Thursday .. Sunday]
[Thursday,Friday,Saturday,Sunday]
ghci> [minBound .. maxBound] :: [Day]
[Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday]
Замечательно!
Синонимы типов
Ранее мы упоминали, что типы [Char]
и String
являются эквивалентами и могут взаимно заменяться. Это осуществляется с помощью синонимов типов. Синоним типа сам по себе ничего не делает – он просто даёт другое имя существующему типу, облегчая понимание нашего кода и документации. Вот так стандартная библиотека определяет тип String
как синоним для [Char]
:
type String = [Char]
Ключевое слово type
может ввести в заблуждение, потому что на самом деле мы не создаём ничего нового (создаём мы с помощью ключевого слова data
), а просто определяем синоним для уже существующего типа.
Если мы создадим функцию, которая преобразует строку в верхний регистр, и назовём её toUpperString
, то можем дать ей сигнатуру типа toUpperString :: [Char] –> [Char]
или toUpperString :: String –> String
. Обе сигнатуры обозначают одно и то же, но вторая легче читается.
Улучшенная телефонная книга
Когда мы работали с модулем Data.Map
, то вначале представляли записную книжку в виде ассоциативного списка, а потом преобразовывали его в отображение. Как мы уже знаем, ассоциативный список – это список пар «ключ–значение». Давайте взглянем на этот вариант записной книжки:
phoneBook :: [(String,String)]
phoneBook =
[("оля","555–29-38")
,("женя","452–29-28")
,("катя","493–29-28")
,("маша","205–29-28")
,("надя","939–82-82")
,("юля","853–24-92")
]
Мы видим, что функция phoneBook
имеет тип [(String,String)]
. Это говорит о том, что перед нами ассоциативный список, который отображает строки в строки, – но не более. Давайте зададим синоним типа, и мы сможем узнать немного больше по декларации типа:
type PhoneBook = [(String,String)]
Теперь декларация типа для нашей записной книжки может быть такой: phoneBook :: PhoneBook
. Зададим также синоним для String
.
type PhoneNumber = String
type Name = String
type PhoneBook = [(Name,PhoneNumber)]
Те, кто программирует на языке Haskell, дают синонимы типу String
, если хотят сделать объявления более «говорящими» – пояснить, чем являются строки и как они должны использоваться.
Итак, реализуя функцию, которая принимает имя и номер телефона и проверяет, есть ли такая комбинация в нашей записной книжке, мы можем дать ей красивую и понятную декларацию типа:
inPhoneBook :: Name –> PhoneNumber –> PhoneBook –> Bool
inPhoneBook name pnumber pbook = (name,pnumber) `elem` pbook
Если бы мы не использовали синонимы типов, тип нашей функции был бы String –> String –> [(String,String)] –> Bool
. В этом случае декларацию функции легче понять при помощи синонимов типов. Однако не надо перегибать палку. Мы применяем синонимы типов для того, чтобы описать, как используются существующие типы в наших функциях (таким образом декларации типов лучше документированы), или когда мы имеем дело с длинной декларацией типа, которую приходится часто повторять (вроде [(String,String)]
), причём эта декларация обозначает что-то более специфичное в контексте наших функций.
Параметризация синонимов