Например мы можем сделать тип для описания паспорта:
data Passport
= Person
{surname
:: String
,-- Фамилия
givenName
:: String
,-- Имя
nationality
:: String
,-- Национальность
dateOfBirth
:: Date
,-- Дата рождения
sex
:: Bool
,-- Пол
placeOfBirth
:: String
,-- Место рождения
authority
:: String
,-- Место выдачи документа
dateOfIssue
:: Date
,-- Дата выдачи
dateOfExpiry
:: Date
-- Дата окончания срока
} deriving
(Eq, Show)--
действия
data Date
= Date
{day
:: Int
,month
:: Int
,year
:: Int
} deriving
(Show, Eq)В фигурных скобках через запятую мы указываем поля. Поле состоит из имени и типа. Теперь нам до-
ступны две операции:
• Чтение полей
hello :: Passport -> String
hello p =
”Hello, ” ++ givenName p ++ ”!”114 | Глава 7: Функторы и монады: примеры
Для чтения мы просто подставляем в имя поля данное значение. В этой функции мы приветствуем
человека и обращаемся к нему по имени. Для того, чтобы узнать его имя мы подсмотрели в паспорт, в
поле givenName.
• Обновление полей. Для обновления полей мы пользуемся таким синтаксисом:
value { fieldName1 =
newValue1, fieldName2 = newValue2, ... }Мы присваиваем в значении value полю с именем fieldName новое значение newFieldValue. К примеру
продлим срок действия паспорта на десять лет:
prolongate :: Passport -> Passport
prolongate p =
p{ dateOfExpiry = newDate }where
newDate = oldDate { year = year oldDate + 10 }oldDate =
dateOfExpiry pВернёмся к типам Sum
и Prod:newtype Sum
a = Sum
{ getSum
::
a }newtype Prod
a = Prod { getProd :: a }Этой записью мы определили два типа-обёртки. У нас есть две функции, которые заворачивают обычное
значение, это Sum
и Prod. С помощью записей мы тут же в определении типа определили функции которыеразворачивают значения, это getSum и getProd.
Вспомним определение для типа State
:data State
s a = State (s -> (a, s))runState :: State
s a -> (s -> (a, s))runState (State
f) = fБыло бы гораздо лучше определить его так:
newtype State
s a = State{ runState :: s -> (a, s) }Накопление чисел
Но вернёмся к нашей задаче. Мы будем накапливать сумму в значении типа Sum
. Поскольку нас интере-сует лишь значение накопителя, наша функция будет возвращать значение единичного типа ().
countBiFuns :: Exp -> Int
countBiFuns =
getSum . execWriter . countBiFuns’countBiFuns’ :: Exp -> Writer
(Sum Int) ()countBiFuns’ x = case
x ofAdd
a b -> tell (Sum 1) *> bi a bMul
a b -> tell (Sum 1) *> bi a bNeg
a->
un a_
->
pure ()where
bi a b = countBiFuns’ a *> countBiFuns’ bun
=
countBiFuns’tell :: Monoid
a => a -> Writer a ()tell a = Writer
((), a)execWriter :: Writer
msg a -> msgexecWriter (Writer
(a, msg)) = msgПервая функция countBiFuns извлекает значение из типов Writer
и Sum. А вторая функция countBiFuns’вычисляет значение.
Мы определили две вспомогательные функции tell, которая записывает сообщение в накопитель и
execWriter, которая возвращает лишь сообщение. Это стандартные для Writer
функции.Посмотрим как работает эта функция:
*Exp>
countBiFuns (n 2)0
*Exp>
countBiFuns (n 2 + n 1 + 2 + 3)3
Накопление результата | 115
Накопление логических значений
В модуле Data.Monoid
определены два типа для накопления логических значений. Это типы All и Any. Спомощью типа All
мы можем проверить выполняется ли некоторое свойство для всех значений. А с помощьютипа Any
мы можем узнать, что существует хотя бы один элемент, для которых это свойство выполнено.Посмотрим на определение экземпляров класса Monoid
для этих типов:newtype All = All
{ getAll :: Bool }instance Monoid All where
mempty = All True
All
x ‘mappend‘ All y = All (x && y)В типе All
мы накапливаем значения с помощью логического “и”. Нейтральным элементом является кон-структор True
. Итоговое значение накопителя будет равно True только в том случае, если все накапливаемыесообщения были равны True
.В типе Any
всё наоборот:instance Monoid Any where
mempty = Any False
Any
x ‘mappend‘ Any y = Any (x || y)Посмотрим как работают эти типы. Составим функцию, которая проверяет отсутствие оператора минус
в выражении:
noNeg :: Exp -> Bool
noNeg =
not . getAny . execWriter . anyNeganyNeg :: Exp -> Writer Any
()anyNeg x = case
x ofNeg _
->
tell (Any True)Add
a b -> bi a bMul
a b -> bi a b_
->
pure ()where
bi a b = anyNeg a *> anyNeg bФункция anyNeg проверяет есть ли в выражении хотя бы один конструктор Neg
. В функции noNeg мыизвлекаем результат и берём его отрицание, чтобы убедиться в том что в выражении не встретилось ни
одного конструктора Neg
.*Exp>
noNeg (n 2 + n 1 + 2 + 3)True
*Exp>
noNeg (n 2 - n 1 + 2 + 3)