Выбрать главу

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

Теперь всё гораздо труднее. Нам всё-таки хочется взаимодействовать с внешним миром. Для обозначения

внешнего мира мы определим специальный тип и назовём его RealWorld:

module IO(

IO

) where

data RealWorld = RealWorld

newtype IO a = IO (ST RealWorld a)

instance Functor

IO where ...

instance Applicative

IO where ...

instance Monad

IO where ...

Тип IO (от англ. input-output или ввод-вывод) обозначает взаимодействие с внешним миром. Внешний

мир словно является состоянием наших вычислений. Экземпляры классов композиции специальных функций

такие же как и для ST (а следовательно и для State). Но при этом, поскольку мы конкретизировали первый

параметр типа ST, мы уже не сможем воспользоваться функцией runST.

Тип RealWorld определён в модуле Control.Monad.ST, там же можно найти и функцию:

stToIO :: ST RealWorld a -> IO a

Интересно, что класс Monad был придуман как раз для решения проблемы ввода-вывода. Классы типов

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

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

такую роль, станут основополагающей особенностью языка.

a

f

IO b

b

g

IO c

До

После

a

g

f

IO c

a

f>>g

IO c

Рис. 8.1: Композиция для монады IO

Посмотрим на (рис. 8.1). Это рисунок для класса Kleisli. Здесь под >> понимается композиция, как мы

её определяли в главе 6, а не метод класса Monad, вспомним определение:

class Kleisli m where

idK

:: a -> m a

(>> ) :: (a -> m b) -> (b -> m c) -> (a -> m c)

Монада IO | 127

Композиция специальных функций типа a -> IO b вносит порядок вычисления. Считается, что сначала

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

счёт скрытой передачи фиктивного состояния. Теперь перейдём к классу Monad. Там композиция заменяется

на применение или операция связывания:

ma >>= mf

Для типа IO эта запись говорит о том, что сначала будет выполнено выражение ma и результат будет под-

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

вида:

a -> IO b

раскалывает наш статический мир на “до” и “после”. Однажды попав в сети IO, мы не можем из них

выбраться, поскольку теперь у нас нет функции runST. Но это не так страшно. Тип IO дробит наш статический

мир на кадры. Но мы спокойно можем создавать статические чистые функции и поднимать их в мир IO лишь

там где это действительно нужно.

Рассмотрим такой пример, программа читает с клавиатуры начальное значение, затем загружает файл

настроек. Потом запускается, какая-то сложная функция и в самом конце мы выводим результат на экран.

Схематично мы можем записать эту программу так:

program = liftA2 algorithm readInit (readConfig ”file”) >>= print

-- функции с побочными эффектами

readInit

:: IO Int

readConfig :: String -> IO Config

print

:: Show a => a -> IO ()

-- большая и сложная, но !чистая! функция

algorithm

:: Int -> Config -> Result

Функция readInit читает начальное значение, функция readConfig читает из файла наcтройки, функ-

ция print выводит значение на экран, если это значение можно преобразовать в строку. Функция algorithm

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

algorithm. В этой схеме мы добавили взаимодействие с пользователем лишь в одном месте, вся функция

algorithm построена по правилам мира описаний. Так мы внесли порядок выполнения в программу, сохра-

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

Если у нас будет ещё один “кадр”, ещё одно действие, например как только функция algorithm закончила

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

вычисления с помощью какой-нибудь другой функции. Тогда наша программа примет вид:

program =

liftA2 algorithm2 readInit

(liftA2 algorithm1 readInit (readConfig ”file”))

>>= print

-- функции с побочными эффектами

readInit