Выбрать главу

сматриваем для класса Monoid. Нам нужно сделать два экземпляра для одного и того же типа Num a

=> a.

Сделаем две обёртки!

newtype Sum

a = Sum

a

newtype Prod a = Prod a

Тогда мы можем определить два экземпляра для двух разных типов:

Один для Sum:

instance Num a => Monoid (Sum a) where

mempty

= Sum 0

mappend (Sum a) (Sum b) = Sum (a + b)

А другой для Prod:

instance Num a => Monoid (Prod a) where

mempty

= Prod 1

mappend (Prod a) (Prod b) = Prod (a * b)

Записи

Вторая новинка заключалась в фигурных скобках. С помощью фигурных скобок в Haskell обозначаются

записи (records). Запись это произведение типа, но с выделенными именами для полей.

Например мы можем сделать тип для описания паспорта:

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 функции.