Эта функция используется при чтении конфигурационных файлов. Если есть уверенность в том, что файл
будет только читаться и во время выполнения программы файл не может быть изменён другой программой,
то мы можем считать, что его значение окажется неизменным на протяжении работы программы. Это говорит
о том, что нам не важно когда читать данные. Поэтому здесь мы вроде бы ничем не рискуем. “Вроде бы”
потому что ответственность за постоянство файла лежит на наших плечах.
Эта функция часто используется при вызове функций С через Haskell. В Haskell есть возможность вызывать
функции, написанные на C. Но по умолчанию такие функции заворачиваются в тип IO
. Если функция являетсячистой в С, то она будет чистой и при вызове через Haskell. Мы можем поручиться за её чистоту и вычислитель
нам поверит. Но если мы его обманули, мы пожнём плоды своего обмана.
138 | Глава 8: IO
Отладка программ
Раз уж речь зашла о “грязных” возможностях языка стоит упомянуть функцию trace из модуля
Debug.Trace
. Посмотрим на её тип:trace :: String ->
a -> aЭто служебная функция эхо-печати. Когда дело доходит до вычисления функции trace на экран выводит-
ся строка, которая была передана в неё первым аргументом, после чего функция возвращает второй аргумент.
Это функция id с побочным эффектом вывода сообщения на экран. Ею можно пользоваться для отладки. На-
пример так можно вернуть значение и распечатать его:
echo :: Show
a => a -> aecho a =
trace (show a) a8.6 Композиция монад
Эта глава завершает наше путешествие в мире типов-монад. Мы начали наше знакомство с монадами с
композиции, мы определили класс Monad
через класс Kleisli, который упрощал составление специальныхфункций вида a ->
m b. Тогда мы познакомились с самыми простыми типами монадами (списки и частичноопределённые функции), потом мы перешли к типам посложнее, мы научились проводить вычисления с
состоянием. В этой главе мы рассмотрели самый важный тип монаду IO
. Мне бы хотелось замкнуть этотрассказ на теме композиции. Мы поговорим о композиции нескольких монад.
Если вы посмотрите в исходный код библиотеки transformers, то увидите совсем другое определение для
State
:type State
s = StateT s Identitynewtype StateT
s m a = StateT { runStateT :: s -> m (a,s) }newtype Identity
a = Identity { runIdentity :: a }Но так ли оно далеко от нашего? Давайте разберёмся. Identity
это тривиальный тип обёртка. Мы простозаворачиваем значение в конструктор и ничего с ним не делаем. Вы наверняка сможете догадаться как опре-
делить экземпляры всех рассмотренных в этой главе классов для этого типа. Тип StateT
больше похож нанаше определение для State
, единственное отличие – это дополнительный параметр m в который завёрнутрезультат функции обновления состояния. Если мы сотрём m, то получим наше определение. Это и сказано
в определении для State
type State
s = StateT s IdentityМы передаём дополнительным параметром в StateT
тип Identity, который как раз ничего и не делаетс типом. Так мы получим наше исходное определение, но зачем такие премудрости? Такой тип принято
называть
данном случае одной из монад является State
. Посмотрим на экземпляр класса Monad для StateTinstance
(Monad m) => Monad (StateT s m) wherereturn 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 wherelift :: Monad
m => m a -> t m aЭтот тип позволяет нам заворачивать специальные значения типа m в значения типа t. Посмотрим на
определение для StateT
:instance MonadTrans
(StateT s) wherelift m = StateT $
\s -> liftM (,s) mКомпозиция монад | 139
Напомню, что функция liftM это тоже самое , что и функция fmap, только она определена через методы
класса Monad
. Мы создали функцию обновлнения состояния, которая ничего не делает с состоянием, а лишьприцепляет его к значению.
Приведём простой пример применения трансформеров. Вернёмся к примеру FSM
из предыдущей главы.Предположим, что наш конечный автомат не только реагирует на действия, но и ведёт журнал, в который
записываются все поступающие на вход события. За переход состояний будет по прежнему отвечать тип State
только теперь он станет трансформером, для того чтобы включить воможность журналирования. За ведение
журнала будет отвечать тип Writer
. Ведь мы просто накапливаем записи.