Интересно, что для добавления новой возможности нам нужно изменить лишь определение типа 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). А в другом вариан-
те алгоритм останется прежним. Но теперь вместо решения найдите список первых приближений до
решения. А затем передайте его в отдельную функцию печати результатов.
В первом варианте алгоритм смешан с печатью. А во втором программа распадается на две части, часть
вычислений и часть вывода результатов на экран. Сравните два подхода.
• Напишите программу для угадывания чисел. Компьютер загадал число в заданном диапазоне и про-
сит вас угадать его. Если вы ошибаетесь он подсказывает: “холодно-горячо” или “больше-меньше”.
Программа принимает два аргумента, которые определяют диапазон возможных значений для неиз-
вестного числа.
• С помощью стандартных функций для генерации случайных чисел напишите программу, которая про-
водит состязание по игре в кости. Программа принимает аргументом суммарное число очков необходи-