Например мы можем сделать тип для описания паспорта:
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 of
Add a b -> tell (Sum 1) *> bi a b
Mul a b -> tell (Sum 1) *> bi a b
Neg a
-> un a
_
-> pure ()
where bi a b = countBiFuns’ a *> countBiFuns’ b
un
= countBiFuns’
tell :: Monoid a => a -> Writer a ()
tell a = Writer ((), a)
execWriter :: Writer msg a -> msg
execWriter (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 . anyNeg
anyNeg :: Exp -> Writer Any ()
anyNeg x = case x of
Neg _
-> tell (Any True)
Add a b -> bi a b
Mul 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)