52700.fb2 Учебник по Haskell - читать онлайн бесплатно полную версию книги . Страница 149

Учебник по Haskell - читать онлайн бесплатно полную версию книги . Страница 149

return a = StateT $ \s -> return (s, a)

a >>= f = StateT $ \s0 ->

runStateT a s0 >>= \(b, s1) -> runStateT (f b) s1

В этом определении мы пропускаем состояние через сито методов класса Monad для типа m. В остальном

это определение ничем не отличается от нашего. Также определены и ReaderT, WriterT, ListT и MaybeT.

Ключевым классом для всех этих типов является класс MonadTrans:

class MonadTrans t where

lift :: Monad m => m a -> t m a

Этот тип позволяет нам заворачивать специальные значения типа m в значения типа t. Посмотрим на

определение для 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) или будете пользоваться библиотеками, которые