не извлекать данные из окружения, а записывать их.
Рассмотрим такую задачу нам нужно обойти дерево типа Expи подсчитать все бинарные операции. Мы
прибавляем к накопителю результата единицу за каждый конструктор Addили Mul. Тип сообщений будет
числом. Нам нужно сделать экземпляр класса Monoidдля чисел.
Напомню, что тип накопителя должен быть экземпляром класса Monoid:
class Monoida where
mempty
::a
mappend ::a ->a ->a
mconcat ::[a] ->a
mconcat =foldr mappend mempty
Но для чисел возможно несколько вариантов, которые удовлетворяют свойствам. Для сложения:
instance Numa => Monoida where
mempty
=0
mappend =( +)
И умножения:
instance Numa => Monoida where
mempty
=1
mappend =( *)
Для нашей задачи подойдёт первый вариант, но не исключена возможность того, что для другой зада-
чи нам понадобится второй. Но тогда мы уже не сможем определить такой экземпляр. Для решения этой
проблемы в модуле Data.Monoidопределено два типа обёртки:
newtype Sum
a = Sum
{ getSum
::a }
newtype Proda = Prod{ getProd ::a }
В этом определении есть два новых элемента. Первый это ключевое слово newtype, а второй это фигурные
скобки. Что всё это значит?
Тип-обёртка newtype
Ключевое слово newtypeвводит новый тип-обёртку. Тип-обёртка может иметь только один конструктор,
у которого лишь одни аргумент. Запись:
newtype Suma = Suma
Это тоже самое, что и
data Suma = Suma
Единственное отличие заключается в том, что в случае newtypeвычислитель не видит разницы между
Suma и a. Её видит лишь компилятор. Это означает, что на разворачивание и заворачивание такого значения
в тип обёртку не тратится никаких усилий. Такие типы подходят для решения двух задач:
• Более точная проверка типов.
Например у нас есть типы, которые описывают физические величины, все они являются числами, но у
них также есть и размерности. Мы можем написать:
type Velocity
= Double
type Time
= Double
type Length
= Double
velocity :: Length -> Time -> Velocity
velocity leng time =leng /time
Накопление результата | 113
В этом случае мы спокойно можем подставить на место времени путь и наоборот. Но с помощью типов
обёрток мы можем исключить эти случаи:
newtype Velocity
= Velocity
Double
newtype Time
= Time
Double
newtype Length
= Length
Double
velocity :: Length -> Time -> Velocity
velocity ( Lengthleng) ( Timetime) = Velocity $leng /time
В этом случае мы проводим проверку по размерностям, компилятор не допустит смешивания данных.
• Определение нескольких экземпляров одного класса для одного типа. Этот случай мы как раз и рас-
сматриваем для класса Monoid. Нам нужно сделать два экземпляра для одного и того же типа Numa
=>a.
Сделаем две обёртки!
newtype Sum
a = Sum
a
newtype Proda = Proda
Тогда мы можем определить два экземпляра для двух разных типов:
Один для Sum:
instance Numa => Monoid( Suma) where
mempty
= Sum0
mappend ( Suma) ( Sumb) = Sum(a +b)
А другой для Prod:
instance Numa => Monoid( Proda) where
mempty
= Prod1
mappend ( Proda) ( Prodb) = 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)
Читать дальше