ДУШКИН Роман Викторович
[email protected] http://roman-dushkin.narod.ru/
ФП 02005-03 01 Джефф Ньюберн (Jeff Newbern)
Взам. инв. № Инв. № дубл.
Подп. и дата
ВСЁ О МОНАДАХ
[email protected] Инв. № подл.
Подп. и дата
http://www.nomaware.com/monads/html/
2006
Копирова
Формат
АННОТАЦИЯ This tutorial aims to explain the concept of a monad and its application to functional programming in a way that is easy to understand and useful to beginning and intermediate Haskell programmers. Familiarity with the Haskell language is assumed, but no prior experience with monads is required. The tutorial covers a lot of material and the later sections require a thorough understanding of the earlier material. Many code examples are provided along the way to demonstrate monadic programming. It is not advisable to attempt to absorb all of the material in a single reading. The tutorial is arranged in three parts. The first part provides a basic understanding of the role of monads in functional programming, how monads operate, and how they are declared and used in Haskell. The second part covers each standard monad in Haskell, giving the definition of the monad and discussing the use of the monad. The third part covers advanced material relating to monad transformers and real-world issues encountered when programming with monads.
Инв. № подл.
Подп. и дата
Взам. инв. № Инв. № дубл.
Подп. и дата
Ключевые слова: монада, стандартные монады, стратегия вычислений.
ФП 02005-03 01 ИзмЛист № докум. Разраб. Душкин Р. Пров. Н. контр. Утв.
Подп. Дата
Лит. Функциональное программирование Копирова
Лист Листов 2 82
Формат
ФП 02005-03 01
СОДЕРЖАНИЕ 1. ПОНЯТИЕ МОНАДЫ..............................................................................................................7 1.1. Введение .............................................................................................................................7 1.1.1. Что такое «монада»?...............................................................................................................7 1.1.2. Зачем пытаться понять монады? ...........................................................................................7
1.2. Знакомство с монадами.....................................................................................................8 1.2.1. 1.2.2. 1.2.3. 1.2.4. 1.2.5.
Конструкторы типов...............................................................................................................8 Из чего состоит монада? ........................................................................................................9 Пример...................................................................................................................................10 Список — тоже монада ........................................................................................................11 Резюме ...................................................................................................................................12
1.3. Работа с классами — классы типов в языке Haskell ....................................................12 1.3.1. 1.3.2. 1.3.3. 1.3.4.
Класс Monad ..........................................................................................................................13 Продолжение примера .........................................................................................................13 Нотация do.............................................................................................................................14 Резюме ...................................................................................................................................15
1.4.1. 1.4.2. 1.4.3. 1.4.4. 1.4.5. 1.4.6.
Три основных закона............................................................................................................16 В случае ошибки ...................................................................................................................16 Монады, не возвращающие значение .................................................................................17 Нулевой элемент и операция «+»........................................................................................18 Резюме ...................................................................................................................................19 Упражнения...........................................................................................................................20
2. СПИСОК СТАНДАРТНЫХ МОНАД ..................................................................................30
2.3. Монада Maybe ..................................................................................................................32
Инв. № подл.
Взам. инв. № Инв. № дубл.
1.5. Поддержка монад в языке Haskell..................................................................................21
Подп. и дата
Подп. и дата
1.4. Законы монад ...................................................................................................................16
1.5.1. В модуле Prelude ...................................................................................................................21 1.5.2. В модуле Monad ....................................................................................................................23 1.5.3. Резюме ...................................................................................................................................29
2.1. Введение ...........................................................................................................................30 2.2. Монада Identity.................................................................................................................31 2.2.1. 2.2.2. 2.2.3. 2.2.4.
Обзор......................................................................................................................................31 Мотивация .............................................................................................................................31 Определение..........................................................................................................................31 Пример...................................................................................................................................32
2.3.1. Обзор......................................................................................................................................32 2.3.2. Мотивация .............................................................................................................................32 2.3.3. Определение..........................................................................................................................32
Лист
ФП 02005-03 01 ИзмЛист № докум.
3
Подп. Дата Копирова
Формат
ФП 02005-03 01
2.3.4. Пример...................................................................................................................................33
2.4. Монада Error ....................................................................................................................33 2.4.1. 2.4.2. 2.4.3. 2.4.4.
Обзор......................................................................................................................................33 Мотивация .............................................................................................................................34 Определение..........................................................................................................................34 Пример...................................................................................................................................35
2.5. Монада List .......................................................................................................................36 2.5.1. 2.5.2. 2.5.3. 2.5.4.
Обзор......................................................................................................................................36 Мотивация .............................................................................................................................36 Определение..........................................................................................................................36 Пример...................................................................................................................................37
2.6. Монада IО.........................................................................................................................38 2.6.1. 2.6.2. 2.6.3. 2.6.4.
Обзор......................................................................................................................................38 Мотивация .............................................................................................................................38 Определение..........................................................................................................................38 Пример...................................................................................................................................40
Подп. и дата
Взам. инв. № Инв. № дубл.
Подп. и дата
2.7. Монада State .....................................................................................................................41 2.7.1. 2.7.2. 2.7.3. 2.7.4.
Обзор......................................................................................................................................41 Мотивация .............................................................................................................................41 Определение..........................................................................................................................41 Пример...................................................................................................................................42
2.8. Монада Reader .................................................................................................................43 2.8.1. 2.8.2. 2.8.3. 2.8.4.
Обзор......................................................................................................................................43 Мотивация .............................................................................................................................43 Определение..........................................................................................................................44 Пример...................................................................................................................................44
2.9. Монада Writer...................................................................................................................46 2.9.1. 2.9.2. 2.9.3. 2.9.4.
Обзор......................................................................................................................................46 Мотивация .............................................................................................................................46 Определение..........................................................................................................................46 Пример...................................................................................................................................48
2.10. 2.10.1. 2.10.2. 2.10.3. 2.10.4.
Монада Continuation ................................................................................................50 Обзор ................................................................................................................................50 Мотивация .......................................................................................................................50 Определение ....................................................................................................................51 Пример .............................................................................................................................52
3. МОНАДЫ В РЕАЛЬНОМ МИРЕ .........................................................................................53 3.1. Introduction........................................................................................................................53 3.2. Combining monads the hard way ......................................................................................53
Инв. № подл.
3.2.1. Nested Monads .......................................................................................................................53
Лист
ФП 02005-03 01 ИзмЛист № докум.
4
Подп. Дата Копирова
Формат
ФП 02005-03 01
3.2.2. Combined Monads..................................................................................................................54
3.3. Monad transformers...........................................................................................................56 3.3.1. Transformer type constructors................................................................................................56 3.3.2. Lifting .....................................................................................................................................57
3.4. Standard monad transformers ............................................................................................59 3.4.1. The MonadTrans and MonadIO classes .................................................................................59 3.4.2. Transformer versions of standard monads..............................................................................59
3.5. Anatomy of a monad transformer......................................................................................60 3.5.1. Combined monad definition ...................................................................................................60 3.5.2. Defining the lifting function ...................................................................................................61 3.5.3. Functors ..................................................................................................................................61
3.6. More examples with monad transformers .........................................................................61 3.6.1. WriterT with IO ......................................................................................................................62 3.6.2. ReaderT with IO .....................................................................................................................63 3.6.3. StateT with List ......................................................................................................................63
3.7. Managing the transformer stack ........................................................................................67
Взам. инв. № Инв. № дубл.
Подп. и дата
3.7.1. Selecting the correct order ......................................................................................................67 3.7.2. An example with multiple transformers .................................................................................67 3.7.3. Heavy lifting ...........................................................................................................................69
3.8. Continuing Exploration .....................................................................................................72 ПРИЛОЖЕНИЕ A .......................................................................................................................73 ПРИЛОЖЕНИЕ B........................................................................................................................77 Example 1 .................................................................................................................................77 Example 2 .................................................................................................................................77 Example 3 .................................................................................................................................77 Example 4 .................................................................................................................................77 Example 5 .................................................................................................................................77 Example 6 .................................................................................................................................78 Example 7 .................................................................................................................................78 Example 8 .................................................................................................................................78 Example 9 .................................................................................................................................78
Инв. № подл.
Подп. и дата
Example 10 ...............................................................................................................................78 Example 11 ...............................................................................................................................79 Example 12 ...............................................................................................................................79 Example 13 ...............................................................................................................................79 Example 14 ...............................................................................................................................79
Лист
ФП 02005-03 01 ИзмЛист № докум.
5
Подп. Дата Копирова
Формат
ФП 02005-03 01
Example 15 ...............................................................................................................................79 Example 16 ...............................................................................................................................80 Example 17 ...............................................................................................................................80 Example 18 ...............................................................................................................................80 Example 19 ...............................................................................................................................80 Example 20 ...............................................................................................................................80 Example 21 ...............................................................................................................................80 Example 22 ...............................................................................................................................81 Example 23 ...............................................................................................................................81 Example 24 ...............................................................................................................................81 Example 25 ...............................................................................................................................81
Инв. № подл.
Подп. и дата
Взам. инв. № Инв. № дубл.
Подп. и дата
Example 26 ...............................................................................................................................81
Лист
ФП 02005-03 01 ИзмЛист № докум.
6
Подп. Дата Копирова
Формат
ФП 02005-03 01
1. ПОНЯТИЕ МОНАДЫ 1.1. Введение 1.1.1. Что такое «монада»? Монада — это способ структурировать вычисления с точки зрения значений и последовательностей вычислений, использующих эти значения. Монады позволяют программисту строить вычисления, используя последовательные модули, которые сами могут быть последовательностями вычислений. Монада определяет, каким образом вычисления комбинируются для создания новых вычислений, тем самым освобождая программиста от необходимости кодировать комбинацию вручную каждый раз, когда она потребуется. Можно считать монаду стратегией комбинирования вычислений в более сложные вычисления. Например, в стандартной поставке языка Haskell имеется тип Maybe:
Инв. № подл.
Подп. и дата
Взам. инв. № Инв. № дубл.
Подп. и дата
data Maybe a = Nothing | Just a
Этот тип представляет собой тип вычислений, которые не всегда могут выдать результат. Тип Maybe предлагает алгоритм комбинирования вычислений, которые возвращают значения типа Maybe: если комбинированное вычисление состоит из вычисления B, которое зависит от результата вычисления А, тогда комбинированное вычисление должно выдавать Nothing в случае, если А или B возвращают Nothing, и комбинированное вычисление должно выдавать результат вычисления B, применённого к результату вычисления А, в случае если А и В завершились успешно. Существуют также монады для построения вычислений, выполняющих операции ввода/вывода, имеющих состояния или возможность возврата множества результатов и т.д. Различных типов монад так же много, как и стратегий комбинирования вычислений, но некоторые из них настолько хороши и удобны, что были включены в стандартные библиотеки Haskell 98. Эти монады подробно описаны во второй части данного руководства.
1.1.2. Зачем пытаться понять монады? Огромное число различных пособий по изучению монад в сети интернет подтверждают проблематичность осваивания людьми данного понятия. Это объясняется абстрактной природой монад и тем фактом, что они используются в нескольких различных качествах, которые могут внести неясность в определение и назначение монад.
Лист
ФП 02005-03 01 ИзмЛист № докум.
7
Подп. Дата Копирова
Формат
ФП 02005-03 01
В языке Haskell монады играют центральную роль в системе ввода/вывода. Не обязательно разбираться в монадах для использования ввода/вывода в Haskell, но понимание монады IO оптимизирует код и расширяет возможности программиста. Для программиста монады — удобный инструмент для структурирования функциональных программ. Существует 3 свойства монад, которые делают их особенно полезными: 1) Модульность — монады позволяют комбинировать вычисления из более простых вычислений и отделять алгоритм комбинирования от фактического выполнения вычислений.
Инв. № подл.
Подп. и дата
Взам. инв. № Инв. № дубл.
Подп. и дата
2) Гибкость — монады позволяют функциональным программам быть гораздо более адаптивными, чем аналогичные программы, написанные без использования монад. Это происходит благодаря тому, что монады помещают стратегию вычислений в одном-единственном месте программы вместо того, чтобы распределять его по всему коду. 3) Изолированность — монады могут быть использованы для создания императивных вычислительных структур, которые остаются изолированными от основного кода функциональной программы. Это полезно для использования побочных эффектов (таких как ввод/вывод) и состояний (которые нарушают прозрачность ссылок) в таком чистом функциональном языке, как Haskell. К каждой из этих особенностей вернёмся позднее при рассмотрении конкретных монад.
1.2. Знакомство с монадами Мы будем использовать конструктор типов Maybe на протяжении этой главы, поэтому прежде, чем идти дальше, необходимо ознакомиться с определением и использованием типа Maybe.
1.2.1. Конструкторы типов Чтобы понять монады в языке Haskell, необходимо хорошо ориентироваться в конструкторах типов. Конструктор типов — это параметризованное определение типа, используемое с полиморфными типами. Применяя конструктор типов к одному или более конкретным типам, можно сконструировать новый конкретный тип в языке Haskell. В определении Maybe: data Maybe a = Nothing | Just a
Монада Maybe — это конструктор типов, а Nothing и Just — конструкторы данных. Можно задать значение данных, применив конструктор данных Just к значению:
Лист
ФП 02005-03 01 ИзмЛист № докум.
8
Подп. Дата Копирова
Формат
ФП 02005-03 01
country = Just "China"
Аналогично можно создать тип, применив конструктор типов Maybe к типу: lookupAge :: DB -> String -> Maybe Int
Полиморфные типы похожи на контейнеры, которые могут содержать значения многих различных типов. Таким образом, Maybe Int можно считать контейнером типа Maybe, содержащим значение типа Int (или Nothing), а Maybe String — контейнером типа Maybe, содержащим значение типа String (или Nothing). В языке Haskell мы также можем сделать тип контейнера полиморфным, поэтому можно написать «m a» чтобы определить контейнер некоторого типа, содержащий значение некоторого типа! Мы часто используем переменные с конструкторами типов для описания абстрактных особенностей вычисления. Например, полиморфный тип Maybe a — это тип всех вычислений, результатом которых может быть значение или Nothing. Таким образом, можно говорить о свойствах контейнера, не упоминая о том, что контейнер может содержать.
Инв. № подл.
Подп. и дата
Взам. инв. № Инв. № дубл.
Подп. и дата
Получение сообщений, подобных «kind errors», от компилятора во время работы с монадами означает некорректное использование конструкторов типов.
1.2.2. Из чего состоит монада? В языке Haskell монада определяется конструктором типов (назовем его m), функцией, формирующей значения данного типа (a -> m a), и функцией, комбинирующей это монадическое значение с вычислениями, которые создают значения данного типа для формирования нового значения данного типа (m a -> (a -> m b) -> m b). Заметьте, что контейнер не меняется, в то время как тип содержимого контейнера может изменяться. Говоря о монадах в целом, принято называть конструктор типов монады m. Функцию, вычисляющую значения данного типа, обычно называют return, а третья функция известна как bind, но записывается как «>>=». Сигнатура функций такова: -- тип монады m data m a = ... -- функция return return :: a -> m a -- bind - функция, связывающая экземпляр монады m a с вычислением, -- которое создает другой экземпляр монады m b из a для создания нового -- экземпляра монады m b (>>=) :: m a -> (a -> m b) -> m b
Лист
ФП 02005-03 01 ИзмЛист № докум.
9
Подп. Дата Копирова
Формат
ФП 02005-03 01
Грубо говоря, конструктор типов монады определяет тип вычисления, функция return создает примитивные значения данного типа вычисления и >>= объединяет вычисления данного типа для проведения более сложных вычислений данного типа. Используя аналогию с контейнерами, можно сказать, что конструктор типов m — это контейнер, который может содержать различные значения. Тип m — это контейнер, содержащий значение типа a. Функция return заносит значение в контейнер монады. Функция >>= берёт значение из контейнера монады и передаёт его функции для создания контейнера монады, содержащего новое значение, возможно, другого типа. Функция известна как bind (связывание), потому что она связывает значение в контейнере монады с первым аргументом функции. При добавлении логических операций в связывающую функцию монада может выполнять определённый алгоритм комбинирования вычислений в монаде. Всё станет понятнее после следующего примера, но если вы чувствуете некоторое замешательство, для начала ознакомьтесь с физической аналогией.
Подп. и дата
Взам. инв. № Инв. № дубл.
Подп. и дата
1.2.3. Пример Предположим, что мы пишем программу, прослеживающую проведение экспериментов по клонированию овец. Конечно, нам захочется знать генетическую историю всех наших овец, поэтому нам понадобятся функции mother и father. Но если овцы клонированы, у них не всегда могут быть и отец, и мать! В нашей реализации на языке Haskell мы опишем возможность отсутствия матери или отца с помощью конструктора типов Maybe: type Sheep = ... father :: Sheep -> Maybe Sheep father = ... mother :: Sheep -> Maybe Sheep mother = ...
Определение функции для нахождения дедушек немного сложнее, потому что необходимо учитывать возможность отсутствия родителя: maternalGrandfather :: Sheep -> Maybe Sheep maternalGrandfather s = case (mother s) of Nothing -> Nothing Just m -> father m
Аналогично для бабушек. Задача становится ещё сложнее, если мы захотим найти прадедов:
Инв. № подл.
mothersPaternalGrandfather :: Sheep -> Maybe Sheep
Лист
ФП 02005-03 01 ИзмЛист № докум.
10
Подп. Дата Копирова
Формат
ФП 02005-03 01
mothersPaternalGrandfather s = case (mother s) of Nothing -> Nothing Just m -> case (father m) of Nothing -> Nothing Just gf -> father gf
Такое описание не только уродливо и непонятно, но и слишком громоздко. Очевидно, что значение Nothing в любой точке вычисления приведёт к Nothing в конечном результате, и гораздо удобнее выполнить данное преобразование один раз и удалить из программы подробные описания проверок case. Данные преобразования упростят создание, чтение и изменение кода программы. Для реализации этих преобразований определим следующий комбинатор: Код содержится в файле example1.hs
Инв. № подл.
Подп. и дата
Взам. инв. № Инв. № дубл.
Подп. и дата
-- comb - комбинатор для упорядочивания операций, производимых Maybe comb :: Maybe a -> (a -> Maybe b) -> Maybe b comb Nothing _ = Nothing comb (Just x) f = f x -- теперь можно использовать `comb` для формирования сложных -- последовательностей mothersPaternalGrandfather :: Sheep -> Maybe Sheep mothersPaternalGrandfather s = (Just s) `comb` mother `comb` father `comb` father
Комбинатор отлично работает! Теперь код программы гораздо аккуратнее, с ним легче работать. Заметьте также, что функция comb полностью полиморфна — она никаким образом не специализирована для типа Sheep. Фактически, комбинатор описывает общую стратегию комбинирования вычислений, которые могут не вернуть значение. Таким образом, можно применять данный комбинатор и к другим вычислениям, не всегда возвращающим значение, таким как запросы к базам данных или поиск слов в словарях. Примечательно, что обычный здравый смысл привёл нас к созданию монады без осознания этого. Конструктор типов Maybe, функция Just (работает как return) и наш комбинатор (работает как >>=) вместе составляют простую монаду для формирования вычислений, которые могут не вернуть значение. Для того, чтобы использовать монаду, остается только согласовать её с встроенным в стандартную библиотеку Haskell набором монад. Этот вопрос является предметом обсуждения в следующей главе.
1.2.4. Список — тоже монада Как мы уже видели, конструктор типов Maybe — это монада для формирования вычислений, которые могут не вернуть значение. Возможно, вы удивитесь, узнав, что
Лист
ФП 02005-03 01 ИзмЛист № докум.
11
Подп. Дата Копирова
Формат
ФП 02005-03 01
другой стандартный конструктор типов в Haskell, [] (для конструирования списков), тоже является монадой. Монада списка позволяет сформировать вычисления, которые дают в результате 0, 1 или более значений. Функция return для списков просто создаёт список единичной длины (return x = [x]). Операция связывания для списков создаёт новый список, который содержит результаты применения функции ко всем значениям в первоначальном списке (l >>= f = concatMap f l). Один из случаев использования функций, возвращающих списки, это представление неоднозначных вычислений, имеющих 0, 1 или более возможных результатов. При проведении неоднозначных вычислений неоднозначность может разрешаться в единственный допустимый результат или вообще не иметь допустимого результата. При этом набор возможных вычислительных состояний представляется списком. Таким образом, списковая монада воплощает собой способ представления одновременных вычислений всех возможных альтернатив неоднозначного вычисления.
Взам. инв. № Инв. № дубл.
Подп. и дата
Примеры данного использования списковой монады, а также сравнительные примеры использования монады Maybe будут представлены далее. Но сначала рассмотрим, как наиболее используемые монады определены в языке Haskell.
1.2.5. Резюме Мы выяснили, что монада состоит из конструктора типов, функции, называемой return, и комбинаторной функции, называемой bind или >>=. Эти три элемента инкапсулируют стратегию комбинирования вычислений для составления более сложных вычислений. С помощью конструктора типов Maybe мы определили простую монаду, которая может использоваться для комбинирования вычислений, не всегда возвращающих значение. Заключив стратегию комбинирования вычислений в монаду, мы добились той степени модульности и гибкости, которая недостижима при обычном объединении вычислений. Также мы увидели, что другой стандартный конструктор типов в Haskell, [], является монадой. Списковая монада инкапсулирует алгоритм комбинирования вычислений, которые могут вернуть 0, 1 или множество значений.
Инв. № подл.
Подп. и дата
1.3. Работа с классами — классы типов в языке Haskell Обсуждение в этой главе включает в себя систему классов типов в языке Haskell. Если вы не знакомы с классами в языке Haskell, вам необходимо предварительно изучить их.
Лист
ФП 02005-03 01 ИзмЛист № докум.
12
Подп. Дата Копирова
Формат
ФП 02005-03 01
1.3.1. Класс Monad В языке Haskell существует стандартный класс Monad, который определяет названия и характеристики двух монадических функций return и >>=. Нет строгой необходимости объявлять ваши монады экземплярами класса Monad, но это приветствуется. Haskell располагает специальными средствами поддержки экземпляров класса Monad, поэтому, если ваши монады будут экземплярами класса Monad, это позволит вам использовать эти средства для написания более чистого и красивого кода. Также объявление монад экземплярами класса Monad несёт важную информацию людям, читающим ваш код. Это легко и имеет массу преимуществ, так что пользуйтесь! Стандартное определение класса Monad в Haskell выглядит примерно так: class Monad m where (>>=) :: m a -> (a -> m b) -> m b return :: a -> m a
1.3.2. Продолжение примера
Инв. № подл.
Подп. и дата
Взам. инв. № Инв. № дубл.
Подп. и дата
Возвращаясь к предыдущему примеру, рассмотрим, как конструктор типов Maybe вписывается в систему монад Haskell в качестве экземпляра класса Monad. Напомню, что наша монада Maybe использует конструктор данных Just в качестве функции return, и мы построили простейший комбинатор, исполняющий роль связывающей функции. Мы можем более явно задать Maybe как монаду, объявив её экземпляром класса Monad: instance Monad Maybe where Nothing >>= f = Nothing (Just x) >>= f = f x return = Just
Определив Maybe экземпляром класса Monad, мы можем использовать стандартные операторы для построения сложных вычислений: -- мы можем использовать монадические операции для построения сложных -- последовательностей maternalGrandfather :: Sheep -> Maybe Sheep maternalGrandfather s = (return s) >>= mother >>= father fathersMaternalGrandmother :: Sheep -> Maybe Sheep fathersMaternalGrandmother s = (return s) >>= father >>= mother >>= mother
Монада Maybe определена как экземпляр класса Monad в модуле Prelude языка Haskell, поэтому вам нет необходимости этим заниматься. Другая монада, с которой мы уже познакомились, конструктор списков, также определена как экземпляр класса Monad в стандартной библиотеке. При написании функций, работающих с монадами,
Лист
ФП 02005-03 01 ИзмЛист № докум.
13
Подп. Дата Копирова
Формат
ФП 02005-03 01
постарайтесь использовать класс Monad вместо конкретного экземпляра класса. Функция типа doSomething :: (Monad m) => a -> m b
гораздо более гибкая, чем doSomething :: a -> Maybe b
Первая из упомянутых функций может использоваться со многими типами монад для получения различного результата в зависимости от алгоритма, заключенного в монаде, в то время как последняя ограничивается алгоритмом монады Maybe.
1.3.3. Нотация do
Подп. и дата
Кроме использования стандартных монадических функций, существует другое преимущество принадлежности вашей монады классу Monad — это поддержка языком Haskell нотации do. Любой экземпляр класса Monad может использоваться в блоке do в Haskell. Короче говоря, нотация do позволяет проводить монадические вычисления, используя псевдо-императивные конструкции с именованными переменными. Результат монадического вычисления может быть «присвоен» переменной с помощью оператора левой стрелки Maybe Sheep mothersPaternalGrandfather s = do m = \_ ->
Все блоки do должны заканчиваться монадическим выражением, клоз let допускается в начале блока do (но клозы let в блоках do не используют ключевое слово in). Определение, описанное выше, может быть преобразовано в следующий код: mothersPaternalGrandfather s =
mother s >>= \m -> father m >>= \gf -> father gf
Теперь становится понятным, почему связывающий оператор так называется. Он используется буквально для связывания значения внутри монады с аргументом последующего λ-выражения.
1.3.4. Резюме Язык Haskell располагает встроенными средствами поддержки монад. Для использования этих средств вам необходимо объявить конструктор типов вашей монады экземпляром класса Monad и предоставить описания функций return и >>= (называемой «связыванием») вашей монады. Монада, являющаяся экземпляром класса Monad, может быть использована с нотацией do, которая является синтаксическим приёмом, обеспечивающим простую императивную запись для описания вычислений с монадами.
Лист
ФП 02005-03 01 ИзмЛист № докум.
15
Подп. Дата Копирова
Формат
ФП 02005-03 01
1.4. Законы монад До этого момента учебное пособие избегало технических подробностей, но есть несколько технических моментов, касающихся монад, которые требуют разъяснения. Монадические операции должны придерживаться законов, известных как «аксиомы монад». Соблюдение данных законов не проверяется компилятором языка Haskell, поэтому обеспечение этого является задачей программиста. Класс Monad в Haskell включает также несколько функций, выходящих за пределы минимального определения, с которыми мы ещё не знакомы. Наконец, многие монады подчиняются дополнительным законам, не входящим в состав стандартных законов монад, и существует дополнительный класс в Haskell для поддержки этих монад.
Подп. и дата
1.4.1. Три основных закона Концепция монады берёт начало в направлении математики, называемом теорией категорий. Хотя нет необходимости в знании теории категорий для создания и использования монад, нам необходимо придерживаться некоторых математических формализмов. Для создания монады недостаточно задать экземпляр класса Monad с корректными характеристиками типов. Для того чтобы монада была составлена правильно, необходимо, чтобы функции return и >>= работали вместе согласно трём правилам: 1) (return x) >>= f == f x 2) m >>= return == m
Инв. № подл.
Подп. и дата
Взам. инв. № Инв. № дубл.
3) (m >>= f) >>= g == m >>= (\x -> f x >>= g) Первое правило требует, чтобы функция return была эквивалентна слева по отношению к >>=. Второе правило требует, чтобы функция return была эквивалентна справа по отношению к >>=. Третье правило представляет собой закон ассоциативности для >>=. Соблюдение этих законов обеспечит непротиворечивость семантики нотации do, использующей монаду. Любой конструктор типов с операторами return и bind, удовлетворяющими трём законам монад, это монада. Компилятор языка Haskell не проверяет соблюдение данных законов для каждого экземпляра класса Monad. Обеспечение соответствия создаваемых монад данным законам — задача программиста.
1.4.2. В случае ошибки Ранее было приведено лишь минимальное определение класса Monad. Полное определение данного класса на самом деле включает ещё две дополнительные функции: fail и >>.
Лист
ФП 02005-03 01 ИзмЛист № докум.
16
Подп. Дата Копирова
Формат
ФП 02005-03 01
Реализация функции fail по умолчанию выглядит так: fail s = error s
Необходимость изменения данного описания функции fail возникает лишь в случае вашего желания обеспечить другой способ обработки ошибки или включить обработку ошибки в вычислительный алгоритм вашей монады. Например, в монаде Maybe так определена функция fail: fail _ = Nothing
Таким образом, функция fail возвращает экземпляр монады Maybe с определённой характеристикой, когда она связывается с другими функциями монады Maybe. Функция fail не является обязательной частью математического определения монады, но она включена в стандартное определение класса Monad из-за роли, которую она исполняет нотации do языка Haskell. Функция fail вызывается каждый раз при неудаче сопоставления с образцом в блоке do:
Инв. № подл.
Подп. и дата
Взам. инв. № Инв. № дубл.
Подп. и дата
fn fn idx
:: Int -> Maybe [Int] = do let l = [Just [1, 2, 3], Nothing, Just [], Just [7..20]] (x:xs) > — оператор, используемый для связывания монадического вычисления, которое не требует результата предыдущего в последовательности вычисления. Её определение с помощью функции >>= выглядит так: (>>) :: m a -> m b -> m b m >> k = m >>= (\_ -> k)
1.4.3. Монады, не возвращающие значение Вы могли заметить, что из монады, являющейся экземпляром стандартного класса Monad, нельзя извлечь значение. Это не проблема. Ничто не мешает автору монады использовать специальные функции. Например, значения могут быть извлечены из монады с помощью сравнения с образцом Just x или использования функции fromJust. Не требуя подобных функций, класс Monad языка Haskell позволяет создавать монады без обратной связи. В такую монаду значение можно передать через функцию return (а иногда и через функцию fail), а также можно выполнить вычисления внутри монады, используя функции связывания >>= и >>. Однако из такой монады нет возможности извлечь значения.
Лист
ФП 02005-03 01 ИзмЛист № докум.
17
Подп. Дата Копирова
Формат
ФП 02005-03 01
Монада IO — один из примеров монад без обратной связи в языке Haskell. Так как вы не можете выйти из монады IO, невозможно написать функцию, выполняющую вычисление внутри монады IO и при этом не имеющей в своём результате конструктора типов IO. Это значит, что любая функция, результирующий тип которой не содержит конструктор типов IO, гарантированно не использует монаду IO. Другие монады, такие как List и Maybe, возвращают значения. Поэтому возможно написание функций, которые используют эти монады, но при этом возвращают не монадические значения.
Инв. № подл.
Подп. и дата
Взам. инв. № Инв. № дубл.
Подп. и дата
Удивительная черта монад без обратной связи состоит в том, что они могут поддерживать побочные эффекты в своих монадических операциях, предотвращая при этом разрушение функциональных свойств немонадических частей программы. Рассмотрим простой пример чтения символа, введенного пользователем. Нельзя написать функцию readChar :: Char, потому что она должна каждый раз при вызове возвращать различный символ, в зависимости от того, что ввёл пользователь. Так как Haskell является чистым функциональным языком, то все функции в нём каждый раз при вызове с одними и теми же аргументами должны возвращать одно и то же значение. Зато можно написать функцию ввода/вывода getChar :: IO Char в монаде IO, потому что она может быть использована лишь в последовательности внутри монады без обратной связи. Нет возможности избавиться от конструктора типов IO в сигнатуре какой-либо функции, использующей его, поэтому конструктор типов IO работает как своего рода тег, определяющий функции, которые выполняют операции ввода/вывода. Более того, такие функции полезны лишь внутри монады IO. Таким образом, монада без обратной связи создаёт изолированный вычислительный процесс, в котором правила чистого функционального языка могут не соблюдаться. Другой стандартный приём при определении монад — это представление монадических значений в качестве функций. Тогда при необходимости получения значения монадического вычисления «выполняется» результирующая монада и выдаёт результат.
1.4.4. Нулевой элемент и операция «+» Кроме трёх основных законов, описанных выше, некоторые монады подчиняются дополнительным правилам. У монад есть специальное значение mzero и оператор mplus, который подчиняется четырём дополнительным законам: 1) mzero >>= f == mzero 2) m >>= (\x -> mzero) == mzero 3) mzero `mplus` m == m 4) m `mplus` mzero == m
Лист
ФП 02005-03 01 ИзмЛист № докум.
18
Подп. Дата Копирова
Формат
ФП 02005-03 01
Легко запомнить законы для mzero и mplus, если сопоставить mzero с 0, mplus с +, и >>= с ∗ в обычной математике. Монады, включающие в себя нулевой элемент и операцию «+», можно объявить экземплярами класса MonadPlus в языке Haskell: class (Monad m) => MonadPlus m where mzero :: m a mplus :: m a -> m a -> m a
Продолжая использовать монаду Maybe в качестве примера, можно сказать, что она является экземпляром класса MonadPlus: instance MonadPlus Maybe where mzero = Nothing Nothing `mplus` x = x x `mplus` _ = x
В данном примере Nothing определяется как нулевое значение, а объединение двух значений Maybe даёт в результате то из этих значений, которое не является Nothing. Если оба входных значения равны Nothing, тогда результатом mplus является также Nothing.
Инв. № подл.
Подп. и дата
Взам. инв. № Инв. № дубл.
Подп. и дата
В монаде списка также есть нулевой элемент и операция «+». Нулевым элементом mzero является пустой список, оператором mplus — оператор конкатенации списков ++. Оператор mplus используется для комбинирования монадических значений из различных вычислений в единственное монадическое значение. В контексте нашего примера клонирования овец можно использовать оператор mplus монады Maybe для определения функции, parent s = (mother s) `mplus` (father s), которая вернёт родителя, если такой есть, или значение Nothing, если у овцы вообще нет родителей. Для овцы с двумя родителями функция mplus вернёт одного из них, в зависимости от точного определения mplus в монаде Maybe.
1.4.5. Резюме Экземпляры класса Monad должны подчиняться так называемым законам монад, которые описывают алгебраические свойства монад. Три основных закона определяют, что функция return эквивалентна справа и слева, и связывающий оператор ассоциативен. Невыполнение этих требований приведет к тому, что монады будут неправильно функционировать, и может вызвать неявные проблемы при использовании нотации do. Помимо функций return и >>=, класс Monad определяет другую функцию, fail. Включение функции fail в монаду не является обязательным, но она удобна в использовании и включена в класс Monad, потому что используется нотацией do языка Haskell.
Лист
ФП 02005-03 01 ИзмЛист № докум.
19
Подп. Дата Копирова
Формат
ФП 02005-03 01
Некоторые монады кроме основных трёх законов подчиняются ещё и дополнительным правилам. Одним из важнейших классов таких монад являются монады с нулевым элементом (mzero) и операцией «+» (mplus). Для монад, в которых есть элемент mzero и оператор mplus, используется класс MonadPlus.
1.4.6. Упражнения Этот раздел содержит несколько простых упражнений для шлифования навыков читателя по созданию монад, а также для укрепления понимания понятия функции и использования монад списка и Maybe перед более глубоким изучением монадического программирования. Упражнения построены на предыдущем примере клонирования овец, с которым читатель уже должен быть знаком.
Упражнение 1: Нотация do Перепишите функции maternalGrandfather, fathersMaternalGrandmother, и mothersPaternalGrandfather из примера 2, используя монадические операторы return и >>=, без помощи нотации do.
Инв. № подл.
Подп. и дата
Взам. инв. № Инв. № дубл.
Подп. и дата
Упражнение 2: комбинирование монадических значений Напишите функции parent и grandparent с сигнатурой Sheep -> Maybe Sheep. Они должны возвращать одну овцу, отобранную от остальных в соответствии с описанием, или значение Nothing, если такой овцы нет. Подсказка: в данном случае удобно воспользоваться оператором mplus.
Упражнение 3: использование монады списка Напишите функции parent и grandparent с сигнатурой Sheep -> [Sheep]. Они должны возвращать всех овец, соответствующих описанию, или пустой список, если таких овец нет. Подсказка: в данном случае удобно использовать оператор mplus из монады списка. Также можно воспользоваться функцией maybeToList, определенной в модуле Maybe, для конвертации значения из типа Maybe в список.
Упражнение 4: Использование ограничения класса Monad Монады обеспечивают модульность и многократное использование кода, инкапсулируя вычислительные алгоритмы в модули, которые могут использоваться для формирования множества различных вычислений. Монады также способствуют модульности, позволяя вам изменять монаду, в которой происходит вычисление, для получения различных вариантов вычислений. Это достигается написанием полиморфных функций в конструкторе типов монады, используя (Monad m) =>, (MonadPlus m) =>, и другие ограничения классов.
Лист
ФП 02005-03 01 ИзмЛист № докум.
20
Подп. Дата Копирова
Формат
ФП 02005-03 01
Напишите функции parent и grandparent с сигнатурой (MonadPlus m) => Sheep > m Sheep. Их удобно использовать в монадах списка и Maybe. Чем различается поведение функций при использовании их с монадой списка и монадой Maybe?
1.5. Поддержка монад в языке Haskell Встроенные в Haskell средства поддержки монад определены в стандартном модуле Prelude, который экспортирует стандартные монадические функции, и в модуле Monad, который содержит реже используемые функции. Конкретные монадические типы содержатся в собственных библиотеках и описаны во второй части данного руководства.
1.5.1. В модуле Prelude Модуль Prelude содержит описание класса Monad и нескольких вспомогательных функций для работы с монадическими типами данных.
1.5.1.1. Класс Monad
Инв. № подл.
Подп. и дата
Взам. инв. № Инв. № дубл.
Подп. и дата
Мы уже встречались с определением класса Monad: class Monad m where (>>=) :: m a -> (>>) :: m a -> return :: a -> m fail :: String
(a -> m b) -> m b m b -> m b a -> m a
-- Минимальное описание: -- (>>=), return m >> k = m >>= \_ -> k fail s = error s
1.5.1.2. Функции установления последовательности Функция sequence берёт список монадических вычислений, по очереди активирует каждое из них и возвращает список результатов. Если какое-либо из вычислений завершается ошибкой, то и вся функция выдает ошибку: sequence sequence
:: =
Monad m => [m a] -> m [a] foldr mcons (return []) where mcons p q = p >>= \x -> q >>= \y -> return (x:y)
Функция sequence_ (обратите внимание на символ нижней черты) работает так же, как и sequence, но не возвращает список результатов. Она используется в случае, если важны только побочные эффекты монадических вычислений. sequence_ sequence_
:: =
Monad m => [m a] -> m () foldr (>>) (return ())
Лист
ФП 02005-03 01 ИзмЛист № докум.
21
Подп. Дата Копирова
Формат
ФП 02005-03 01
1.5.1.3. Отображающие функции Функция mapM применяет монадическое вычисление ко всем элементам списка и возвращает список результатов. Она определена с помощью функции map и функции sequence, описанной выше: mapM mapM f as
:: =
Monad m => (a -> m b) -> [a] -> m [b] sequence (map f as)
Для данной функции также существует версия с символом нижней черты, mapM_, которая определяется с помощью функции sequence_. Функция mapM_ работает так же, как и mapM, но не возвращает список результатов. Она используется в случае, если важны только побочные эффекты монадических вычислений. mapM_ mapM_ f as
:: =
Monad m => (a -> m b) -> [a] -> m () sequence_ (map f as)
Являясь простым примером использования отображающих функций, функция putString для монады IO может быть определена следующим образом:
Инв. № подл.
Подп. и дата
Взам. инв. № Инв. № дубл.
Подп. и дата
putString putString
:: s
[Char] -> IO () = mapM_ putChar s
Функция mapM может использоваться внутри блока do примерно так же, как функция map обычно используется со списками. Это распространенная ситуация — версия функции для использования внутри монады (т.е. рассчитанная на связывание) имеет сигнатуру, схожую с не монадической версией, но возвращаемые функцией значения находятся внутри монады: -- сравните монадическую и не монадическую сигнатуры map :: (a -> b) -> [a] -> [b] mapM :: Monad m => (a -> m b) -> [a] -> m [b]
1.5.1.4. Функция обратного связывания (==», обратная связывающая функция обозначается «= m b f =>= f
Лист
ФП 02005-03 01 ИзмЛист № докум.
22
Подп. Дата Копирова
Формат
ФП 02005-03 01
1.5.2. В модуле Monad Модуль Monad в стандартных библиотеках Haskell 98 экспортирует некоторые средства для более сложных монадических операций. Для использования этих средств нужно импортировать модуль Monad в вашу программу на языке Haskell. В данной главе описаны не все функции, представленные в модуле Monad, поэтому приветствуется более подробное самостоятельное изучение данного модуля.
1.5.2.1. Класс MonadPlus В модуле Monad определен класс MonadPlus для монад с нулевым элементом и операцией «+»: class Monad m => MonadPlus m where mzero :: m a mplus :: m a -> m a -> m a
1.5.2.2. Монадические версии списковых функций
Подп. и дата
Однако проще понять принцип работы функции foldM, если рассмотреть её определение, записанное с помощью блока do:
Инв. № подл.
Подп. и дата
Функция foldM — это монадическая версия функции foldl. Определение функции foldM:
Взам. инв. № Инв. № дубл.
В модуле Monad определено несколько функций, обобщающих стандартные функции работы со списками на монады. Функции mapM экспортируются модулем Prelude и описаны выше.
foldM foldM f a [] foldM f a (x:xs)
:: = =
(Monad m) => (a -> b -> m a) -> a -> [b] -> m a return a f a x >>= \y -> foldM f y xs
-- данный код не является допустимым в языке Haskell; -- это всего лишь иллюстрация. foldM f a1 [x1, x2, ..., xn] = do a2 Maybe Sheep) ] -> Maybe Sheep
Лист
ФП 02005-03 01 ИзмЛист № докум.
23
Подп. Дата Копирова
Формат
ФП 02005-03 01
traceFamily s l
=
foldM getParent s l where getParent s f = f s
-- используя traceFamily, можно легко составлять сложные запросы mothersPaternalGrandfather s = traceFamily s [mother, father, father] paternalGrandmother s = traceFamily s [father, mother]
Функция traceFamily использует foldM для создания простого способа просмотра родового дерева на любую глубину и в любом направлении. Фактически, проще написать traceFamily s [father, mother], чем использовать функцию paternalGrandmother! Использование функции foldM в блоке do более характерно: Код содержится в файле example4.hs -- Dict - просто конечное отображение из строк в строки type Dict = FiniteMap String String
Инв. № подл.
Подп. и дата
Взам. инв. № Инв. № дубл.
Подп. и дата
-- вспомогательная функция, используемая с foldl addEntry :: Dict -> Entry -> Dict addEntry d e = addToFM d (key e) (value e) -- вспомогательная функция, используемая с foldM внутри монады IO addDataFromFile :: Dict -> Handle -> IO Dict addDataFromFile dict hdl = do contents [a] -> [b] -> m [c] sequence (zipWith f xs ys)
zipWithM_ zipWithM_ f xs ys
:: =
(Monad m) => (a -> b -> m c) -> [a] -> [b] -> m () sequence_ (zipWith f xs ys)
1.5.2.3. Условные монадические вычисления Существует две функции, обеспечивающие условное выполнение монадических вычислений. Функция when берёт аргумент типа Boolean и монадическое вычисление с модулем типа () и производит вычисление только в том случае, если значение аргумента типа Boolean равно True. Функция unless работает аналогично, но она производит вычисление, пока значение типа Boolean не равно True. when when p s
:: =
(Monad m) => Bool -> m () -> m () if p then s else return ()
unless unless p s
:: =
(Monad m) => Bool -> m () -> m () when (not p) s
Инв. № подл.
Подп. и дата
1.5.2.4. Функция ap и втягивающие функции Втягивание — это монадическая операция конвертации немонадической функции в эквивалентную функцию, которая оперирует монадическими значениями. Говорят, что функция «втянута в монаду» операторами втягивания. Втянутая функция используется для оперирования монадическими значениями вне блока do, а также для сокращения кода внутри блока do.
Лист
ФП 02005-03 01 ИзмЛист № докум.
25
Подп. Дата Копирова
Формат
ФП 02005-03 01
liftM — это простейший втягивающий оператор, который втягивает в монаду функцию одного аргумента. liftM liftM f
:: =
(Monad m) => (a -> b) -> (m a -> m b) \a -> do { a' do defs a) function performs a computation in the Reader monad and returns the result of type a.
Лист
ФП 02005-03 01 ИзмЛист № докум.
56
Подп. Дата Копирова
Формат
ФП 02005-03 01
A transformer version of the Reader monad, called ReaderT, exists which adds a monad type constructor as an addition parameter. ReaderT r m a is the type of values of the combined monad in which Reader is the base monad and m is the inner monad. ReaderT r m is an instance of the monad class, and the runReaderT::(r -> m a) function performs a computation in the combined monad and returns a result of type m a. Using the transformer versions of the monads, we can produce combined monads very simply. ReaderT r IO is a combined Reader+IO monad. We can also generate the nontransformer version of a monad from the transformer version by applying it to the Identity monad. So ReaderT r Identity is the same monad as Reader r. If your code produces kind errors during compilation, it means that you are not using the type cosntructors properly. Make sure that you have supplied the correct number of parameters to the type constructors and that you have not left out any parenthesis in complex type expressions.
Инв. № подл.
Подп. и дата
Взам. инв. № Инв. № дубл.
Подп. и дата
3.3.2. Lifting When using combined monads created by the monad transformers, we avoid having to explicitly manage the inner monad types, resulting in clearer, simpler code. Instead of creating additional do-blocks within the computation to manipulate values in the inner monad type, we can use lifting operations to bring functions from the inner monad into the combined monad. Recall the liftM family of functions which are used to lift non-monadic functions into a monad. Each monad transformer provides a lift function that is used to lift a monadic computation into a combined monad. Many transformers also provide a liftIO function, which is a version of lift that is optimized for lifting computations in the IO monad. To see this in action, we will continue to develop our previous example in the Continuation monad. Code available in example21.hs fun :: IO String fun = (`runContT` return) $ do n c >>= (\x -> return (x,s))
The lift function creates a StateT state transformation function that binds the computation in the inner monad to a function that packages the result with the input state. The result is that a function that returns a list (i.e., a computation in the List monad) can be lifted into StateT s [], where it becomes a function that returns a StateT (s -> [(a,s)]). That is, the lifted computation produces multiple (value,state) pairs from its input state. The effect of this is to "fork" the computation in StateT, creating a different branch of the computation for each value in the list returned by the lifted function. Of course, applying StateT to a different monad will produce different semantics for the lift function.
3.5.3. Functors We have examined the implementation of one monad transformer above, and it was stated earlier that there was no magic formula to produce transformer versions of monads. Each transformer's implementation will depend on the nature of the computational effects it is adding to the inner monad. Despite this, there is some theoretical foundation to the theory of monad transformers. Certain transformers can be grouped according to how they use the inner monad, and the transformers within each group can be derived using monadic functions and functors. Functors, roughly, are types which support a mapping operation fmap :: (a->b) -> f a -> f b. To learn more about it, check out Mark Jones' influential paper that inspired the Haskell monad template library.
3.6. More examples with monad transformers At this point, you should know everything you need to begin using monads and monad transformers in your programs. The best way to build proficiency is to work on actual code. As
Лист
ФП 02005-03 01 ИзмЛист № докум.
61
Подп. Дата Копирова
Формат
ФП 02005-03 01
your monadic programs become more abitious, you may find it awkward to mix additional transformers into your combined monads. This will be addressed in the next section, but first you should master the basic process of applying a single transformer to a base monad.
3.6.1. WriterT with IO Try adapting the firewall simulator of example 17 to include a timestamp on each log entry (don't worry about merging entries). The necessary changes should look something like this: Code available in example22.hs -- this is the format of our log entries data Entry = Log {timestamp::ClockTime, msg::String} deriving Eq instance Show Entry where show (Log t s) = (show t) ++ " | " ++ s
Инв. № подл.
Подп. и дата
Взам. инв. № Инв. № дубл.
Подп. и дата
-- this is the combined monad type type LogWriter a = WriterT [Entry] IO a -- add a message to the log logMsg :: String -> LogWriter () logMsg s = do t Packet -> LogWriter (Maybe Packet) filterOne rules packet = do rule do logMsg ("DROPPING UNMATCHED PACKET: " ++ (show packet)) return Nothing (Just r) -> do when (logIt r) (logMsg ("MATCH: " ++ (show r) ++ " " ++ (show packet))) case r of (Rule Accept _ _) -> return (Just packet) (Rule Reject _ _) -> return Nothing -- this filters a list of packets, producing a filtered packet list -- and a log of the activity filterAll :: [Rule] -> [Packet] -> LogWriter [Packet] filterAll rules packets = do logMsg "STARTING PACKET FILTER" out Predicate -> Predicate orElse a b = (a `And` (Not b)) `Or` ((Not a) `And` b) -- Check a predicate with the given variable bindings. -- An unbound variable causes a Nothing return value. check :: Predicate -> Variables -> Maybe Bool check (Is var value) vars = do val ProblemState -> [a] getAllSolutions c i = evalStateT c i
We are ready to apply the predicate language and stateful nondeterministic monad to solving a logic problem. For this example, we will use the well-known Kalotan puzzle which appeared in Mathematical Brain-Teasers, Dover Publications (1976), by J. A. H. Hunter. The Kalotans are a tribe with a peculiar quirk: their males always tell the truth. Their females never make two consecutive true statements, or two consecutive untrue statements. An anthropologist (let's call him Worf) has begun to study them. Worf does not yet know the Kalotan language. One day, he meets a Kalotan (heterosexual) couple and their child Kibi. Worf asks Kibi: ``Are you a boy?'' The kid answers in Kalotan, which of course Worf doesn't understand. Worf turns to the parents (who know English) for explanation. One of them says:
Лист
ФП 02005-03 01 ИзмЛист № докум.
65
Подп. Дата Копирова
Формат
ФП 02005-03 01
"Kibi said: `I am a boy.'" The other adds: "Kibi is a girl. Kibi lied." Solve for the sex of Kibi and the sex of each parent. We will need some additional predicates specific to this puzzle, and to define the universe of allowed variables values: Code available in example24.hs -- if a male says something, it must be true said :: Var -> Predicate -> Predicate said v p = (v `Is` "male") `implies` p -- if a male says two things, they must be true -- if a female says two things, one must be true and one must be false saidBoth :: Var -> Predicate -> Predicate -> Predicate saidBoth v p1 p2 = And ((v `Is` "male") `implies` (p1 `And` p2)) ((v `Is` "female") `implies` (p1 `orElse` p2))
Инв. № подл.
Подп. и дата
Взам. инв. № Инв. № дубл.
Подп. и дата
-- lying is saying something is true when it isn't or saying something isn't true when it is lied :: Var -> Predicate -> Predicate lied v p = ((v `said` p) `And` (Not p)) `orElse` ((v `said` (Not p)) `And` p) -- Test consistency over all allowed settings of the variable. tryAllValues :: Var -> NDS () tryAllValues var = do (setVar var "male") `mplus` (setVar var "female") c Error e (a,s). Applying ErrorT to the State monad gives a state transformer function of type s -> (Error e a,s). Which order to choose depends on the role of errors in your computation. If an error means no state could be produced, you would apply StateT to Error. If an error means no value could be produced, but the state remains valid, then you would apply ErrorT to State. Choosing the correct order requires understanding the transformation carried out by each monad transformer, and how that transformation affects the semantics of the combined monad.
3.7.2. An example with multiple transformers The following example demonstrates the use of multiple monad transformers. The code uses the StateT monad transformer along with the List monad to produce a combined monad for doing stateful nondeterministic computations. In this case, however, we have added the WriterT monad transformer to perform logging during the computation. The problem we will apply this monad to is the famous N-queens problem: to place N queens on a chess board so that no queen can attack another. The first decision is in what order to apply the monad transformers. StateT s (WriterT w []) yields a type like: s -> [((a,s),w)]. WriterT w (StateT s []) yields a type like: s -> [((a,w),s)]. In this case, there is little difference between the two orders, so we will choose the second arbitrarily.
Лист
ФП 02005-03 01 ИзмЛист № докум.
67
Подп. Дата Копирова
Формат
ФП 02005-03 01
Our combined monad is an instance of both MonadState and MonadWriter, so we can freely mix use of get, put, and tell in our monadic computations. Code available in example25.hs -- this is the type of our problem description data NQueensProblem = NQP {board::Board, ranks::[Rank], files::[File], asc::[Diagonal], desc::[Diagonal]} -- initial state = empty board, all ranks, files, and diagonals free initialState = let fileA = map (\r->Pos A r) [1..8] rank8 = map (\f->Pos f 8) [A .. H] rank1 = map (\f->Pos f 1) [A .. H] asc = map Ascending (nub (fileA ++ rank1)) desc = map Descending (nub (fileA ++ rank8)) in NQP (Board []) [1..8] [A .. H] asc desc
Инв. № подл.
Подп. и дата
Взам. инв. № Инв. № дубл.
Подп. и дата
-- this is our combined monad type for this problem type NDS a = WriterT [String] (StateT NQueensProblem []) a -- Get the first solution to the problem, by evaluating the solver computation with -- an initial problem state and then returning the first solution in the result list, -- or Nothing if there was no solution. getSolution :: NDS a -> NQueensProblem -> Maybe (a,[String]) getSolution c i = listToMaybe (evalStateT (runWriterT c) i) -- add a Queen to the board in a specific position addQueen :: Position -> NDS () addQueen p = do (Board b) [a] -> WriterT [String] [] a logEach xs = do x Int -> m () addVal n = do x NDS () setVal n = do x NDS () addDigits n = do x NDS a liftListToNDS = lift . lift -- perform a series of computations in the combined monad, lifting computations from other -- monads as necessary. main :: IO () main = do mapM_ print $ runWriterT $ (`evalStateT` 0) $ do x Tray Chopsticks polishChopsticks c = ... -- worker function 3: wraps chopsticks wrapChopsticks :: Chopsticks -> Tray Wrapper Chopsticks wrapChopsticks c = ...
Лист
ФП 02005-03 01 ИзмЛист № докум.
74
Подп. Дата Копирова
Формат
ФП 02005-03 01
It is clear that the worker machines contain all of the functionality needed to produce chopsticks. What is missing is the specification of the trays, loader, and combiner machines that collectively make up the Tray monad. Our trays should either be empty or contain a single item. Our loader machine would simply take an item and place it in a tray on the conveyor belt. The combiner machine would take each input tray and pass along empty trays while feeding the contents of non-empty trays to its worker machine. In Haskell, we would define the Tray monad as: -- trays are either empty or contain a single item data Tray x = Empty | Contains x -- Tray is a monad instance Monad Tray where Empty >>= _ (Contains x) >>= worker return fail _
= = = =
Empty worker x Contains Empty
You may recognize the Tray monad as a disguised version of the Maybe monad that is a standard part of Haskell 98 library.
Инв. № подл.
Подп. и дата
Взам. инв. № Инв. № дубл.
Подп. и дата
All that remains is to sequence the worker machines together using the loader and combiner machines to make a complete assembly line, as shown in Figure A-2.
Figure A-2. A complete assembly line for making chopsticks using a monadic approach.
In Haskell, the sequencing can be done using the standard monadic functions: assemblyLine :: Wood -> Tray Wrapped Chopsticks assemblyLine w = (return w) >>= makeChopsticks >>= polishChopsticks >>= wrapChopsticks
or using the built in Haskell "do" notation for monads: assemblyLine :: Wood -> assemblyLine w = do c c' c''
Tray Wrapped Chopsticks >= _ (Contains x) >>= worker return fail reason
= = = =
Failed reason worker x Contains Failed reason
You may recognize the Tray2 monad as a disguised version of the Error monad that is a standard part of the Haskell 98 libraries. Replacing the Tray monad with the Tray2 monad instantly upgrades your assembly line. Now when a failure occurs, the tray that is brought to the quality control engineer contains a failure report detailing the exact cause of the failure!
Инв. № подл.
Подп. и дата
Взам. инв. № Инв. № дубл.
Подп. и дата
You realize that your job would be much easier if you took advantage of the failure messages that are currently ignored by the fail routine in your Tray monad. Because your assembly line is organized around a monadic approach, it is easy for you to add this functionality to your assembly line without changing your worker machines.
Лист
ФП 02005-03 01 ИзмЛист № докум.
76
Подп. Дата Копирова
Формат
ФП 02005-03 01
Приложение B (обязательное)
HASKELL CODE EXAMPLES This appendix contains a list of all of the code examples supplied with the tutorial.
Example 1 This example is discussed in the section: Meet the monads. The example code introduces the monad concept without using Haskell typeclasses. It shows how a monadic combinator can be used to simplify the construction of computations from sequences of computations which may not return a result.
Example 2
Инв. № подл.
Подп. и дата
Взам. инв. № Инв. № дубл.
Подп. и дата
This example is discussed in the section: Doing it with class. The example code builds on the first example, and shows how do-notation can be used with an instance of the Monad class (in this case, Maybe is the monad used).
Example 3 This example is discussed in the section: Monad support in Haskell. The example code builds on the first two examples, and shows a somewhat atypical — but very powerful — use of the foldM function outside of a do-block.
Example 4 This example is discussed in the section: Monad support in Haskell. The example code shows a more typical use of the foldM function within a do-block. It combines dictionary values read from different files into a single dictionary using the foldM function within the IO monad.
Example 5 This example is discussed in the section: Monad support in Haskell.
Лист
ФП 02005-03 01 ИзмЛист № докум.
77
Подп. Дата Копирова
Формат
ФП 02005-03 01
The example code shows the use of the filterM function within a do-block. It prints the subset of its arguments that specify directories and ignores non-directory arguments.
Example 6 This example is discussed in the section: Monad support in Haskell. The example code shows the use of the liftM function within a do-block. It looks up a name in a list and uses a lifted String manipulation function to modify it within the Maybe monad.
Example 7 This example is discussed in the section: Monad support in Haskell.
Взам. инв. № Инв. № дубл.
Подп. и дата
The example code shows a higher-order application of liftM2. It folds lifted operations within the List monad to produce lists of all combinations of elements combined with the lifted operator.
Example 8 This example is discussed in the section: Monad support in Haskell. The example code shows a higher-order application of ap. It folds ap through a list of Maybe (a->a) functions to process sequences of commands.
Example 9 This example is discussed in the section: Monad support in Haskell. The example code shows the use of msum in the Maybe monad to select the first variable match in a stack of binding environments.
Example 10
Инв. № подл.
Подп. и дата
This example is discussed in the section: Monad support in Haskell. The example code shows the use of guard in the Maybe monad to select only the records from a list that satisfy a predicate (equivalent to filter).
Лист
ФП 02005-03 01 ИзмЛист № докум.
78
Подп. Дата Копирова
Формат
ФП 02005-03 01
Example 11 This example is discussed in the section: The Maybe monad. The example code shows how to use the Maybe monad to build complex queries from simpler queries that may fail to return a result. The specific example used is looking up mail preferences for someone based on either their full name or a nickname.
Example 12 This example is discussed in the section: The Error monad. The example code demonstrates the use of the Either type constructor as an Error monad with a custom error type. The example parses hexadecimal digits and uses the exception handling mechanism of the Error monad to provide informative error messages in the event of a parse failure.
Example 13
Взам. инв. № Инв. № дубл.
Подп. и дата
This example is discussed in the section: The List monad. The example code uses the built-in list type constructor as a monad for non-deterministic computation. The example demonstrates parsing an ambiguous grammar consisting of integers, hex values, and words.
Example 14 This example is discussed in the section: The IO monad. The example code implements a simple version of the standard Unix command "tr". The example demonstrates use of the IO monad including implicit fail calls due to pattern matching failures and the use of catcherror.
Example 15
Инв. № подл.
Подп. и дата
This example is discussed in the section: The State monad. The example code shows how the State monad can be used instead of explicitly passing state. The example uses the State monad to manage the random number generator state while building a compound data value requiring multiple calls to the random number generator.
Лист
ФП 02005-03 01 ИзмЛист № докум.
79
Подп. Дата Копирова
Формат
ФП 02005-03 01
Example 16 This example is discussed in the section: The Reader monad. The example code shows how the Reader monad can be used to simplify computations involving a shared environment. The example uses the Reader monad to implement a simple template substitution system. The example code demonstrates the use of the Parsec monadic parser combinator library.
Example 17 This example is discussed in the section: The Writer monad. The example code shows how the Writer monad can be used to implement logging. The example implements a very simple firewall simulator and uses the Writer monad to log the firewall activity.
Example 18
Подп. и дата
Взам. инв. № Инв. № дубл.
Подп. и дата
This example is discussed in the section: The Continuation monad. The example code shows how the Continuation monad's escape continuations work. The example computes a complex transformation of a number.
Example 19 This example is discussed in the section: Combining monads the hard way. The example code shows how the Continuation monad can be nested within the IO monad given a suitable computational structure. The example is a slight modification of example 18.
Example 20 This example is discussed in the section: Combining monads the hard way. The example code shows how the Continuation monad and IO monad can be used simultaneously, but without using monad transformers. The example builds on examples 18 and 19.
Example 21
Инв. № подл.
This example is discussed in the section: Monad transformers.
Лист
ФП 02005-03 01 ИзмЛист № докум.
80
Подп. Дата Копирова
Формат
ФП 02005-03 01
The example code shows how the transformer version of the Continuation monad can be used to create a combined monad for using continuations and doing I/O. The example builds on examples 18, 19 and 20.
Example 22 This example is discussed in the section: Standard monad transformers. The example code shows how the transformer version of the Writer monad can be used to create a combined monad for logging and doing I/O. The example adds timestamps to the log entries of the firewall simulator from example 17.
Example 23 This example is discussed in the section: Standard monad transformers.
Инв. № подл.
Подп. и дата
Взам. инв. № Инв. № дубл.
Подп. и дата
The example code shows how the transformer version of the Reader monad can be used to create a monad that combines a shared environment with I/O. The example converts the template system of example 16 to use files as templates.
Example 24 This example is discussed in the section: Standard monad transformers. The example code uses the StateT transformer on the List monad to create a combined monad for doing non-deterministic stateful computations. The example uses the combined monad to solve a logic problem.
Example 25 This example is discussed in the section: An example with multiple monad transformers. The example code uses the StateT and WriterT transformers on the List monad to create a combined monad for doing non-deterministic stateful computations with logging. The example uses the combined monad to solve the N-queens problem.
Example 26 This example is discussed in the section: Heavy lifting. The example code demonstrates the use of the lift function and the necessity of managing its use in complex transformer stacks.
Лист
ФП 02005-03 01 ИзмЛист № докум.
81
Подп. Дата Копирова
Формат
Лист регистрации изменений Номера листов (страниц) Изм. изменённых
заменённых
новых
аннулированных
Всего листов (страниц) в докум.
№ документа
Входящий № сопроводительного докум. и дата
Подп.
Дата