haskell-notes

определение для StateT:

instance MonadTrans (StateT s) where

lift m = StateT $ s -> liftM (,s) m

Композиция монад | 139

Напомню, что функция liftM это тоже самое , что и функция fmap, только она определена через методы

класса Monad. Мы создали функцию обновлнения состояния, которая ничего не делает с состоянием, а лишь

прицепляет его к значению.

Приведём простой пример применения трансформеров. Вернёмся к примеру FSM из предыдущей главы.

Предположим, что наш конечный автомат не только реагирует на действия, но и ведёт журнал, в который

записываются все поступающие на вход события. За переход состояний будет по прежнему отвечать тип State

только теперь он станет трансформером, для того чтобы включить воможность журналирования. За ведение

журнала будет отвечать тип Writer. Ведь мы просто накапливаем записи.

Интересно, что для добавления новой возможности нам нужно изменить лишь определение типа FSM и

функцию fsm, теперь они примут вид:

module FSMt where

import Control.Monad.Trans.Class

import Control.Monad.Trans.State

import Control.Monad.Trans.Writer

import Data.Monoid

type FSM s = StateT s (Writer [String]) s

fsm :: Show ev => (ev -> s -> s) -> (ev -> FSM s)

fsm transition e = log e >> run e

where run e = StateT $ s -> return (s, transition e s)

log e = lift $ tell [show e]

Все остальные функции останутся прежними. Сначала мы подключили все необходимые модули из биб-

лиотеки transformers. В подфункции log мы сохраняем сообщение в журнал, а в подфункции run мы вы-

полняем функцию перехода. Посмотрим, что у нас получилось:

*FSMt> let res = mapM speaker session

*FSMt> runWriter $ runStateT res (Sleep, Level 2)

(([(Sleep, Level 2),(Work, Level 2),(Work, Level 3),(Work, Level 2),

(Sleep, Level 2)],(Sleep, Level 3)),

[”Button”,”Louder”,”Quieter”,”Button”,”Louder”])

*FSMt> session

[Button, Louder, Quieter, Button, Louder]

Мы видим, что цепочка событий была успешно записана в журнал.

Для трансформеров с типом IO определён специальный класс:

class Monad m => MonadIO m where

liftIO :: IO a -> m a

Этот класс живёт в модуле Control.Monad.IO.Class. С его помощью мы можем выполнять IO-действия

ввнутри другой монады. Эта возможность бывает очень полезной. Вам она обязательно понадобится, если вы

начнёте писать веб-сайты на Haskell (например в happstack) или будете пользоваться библиотеками, которые

надстроены над C (например физический движок Hipmunk).

8.7 Краткое содержание

Наконец-то мы научились писать программы! Программы, которые можно исполнять за пределами ин-

терпретатора. Для этого нам пришлось познакомиться с типом IO. Экземпляр класса Monad для этого типа

интерпретируется специальным образом. Он вносит упорядоченность в выполнение программы. В нашем

статическом мире описаний появляются такие понятия как “сначала”, “затем”, “до” и “после”. Но они не

смогут нанести много вреда.

Вычисление операций с побочными эффектами разбивает программу на кадры. Но это не мешает нам

писать основные функции в чистом виде, подставляя их по мере необходимости в изменчивый мир побочных

эффектов с помощью методов из классов Functor, Applicative, Monad.

Мы узнали как в Haskell обстоят дела с такими типичными задачами мира побочных эффектов как

ввод/вывод, чтение/запись файлов, генерация случайных значений, выполнение внешних программ, ини-

циализация программ с помощью флагов. Также мы узнали о том, как обрабатываются специфические для

типа IO исключения.

140 | Глава 8: IO

8.8 Упражнения

Старайтесь свести присутствие функций с побочными эффектами к минимуму. Идеальный случай, когда

тип IO встречается только в функции main. Часто программы устроены более хитрым образом и функции

с побочными эффектами пытаются расползтись по всему коду. Но даже в этом случае программу можно

разделить на две части: в одной живут подлинные источники побочных эффектов, такие как чтение файлов,

генерация случайных значений, а в другой – чистые функции. Старайтесь устроить программу так, чтобы

она была максимально чистой. Чистые функции гораздо проще комбинировать, понимать, изменять.

• Это упражнение даёт вам возможность почувствовать преимущества чистого кода. Вспомните функ-

цию поиска корней методом неподвижной точки (этот пример встречался в главе о ленивых вычисле-

ниях). Напишите на основе этого примера программу, которая будет распечатывать решение и после-

довательность приближений. Последовательность приближений состоит из текущего значения корня

и расстоянии между корнями.

Напишите два варианта программы, в одном вы измените алгоритм так, чтобы печать происходила

одновременно с вычислениями (не пользуясь функцией из модуля Debug.Trace). А в другом вариан-

те алгоритм останется прежним. Но теперь вместо решения найдите список первых приближений до

решения. А затем передайте его в отдельную функцию печати результатов.

В первом варианте алгоритм смешан с печатью. А во втором программа распадается на две части, часть

вычислений и часть вывода результатов на экран. Сравните два подхода.

• Напишите программу для угадывания чисел. Компьютер загадал число в заданном диапазоне и про-

сит вас угадать его. Если вы ошибаетесь он подсказывает: “холодно-горячо” или “больше-меньше”.

Страницы: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162