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

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

purge (Mutable a) = fst $ runState a FakeState

new

:: a -> Mutable (Mem a)

read

:: Mem a -> Mutable a

write

:: Mem a -> a -> Mutable ()

Мы предоставим пользователю лишь тип Mutable без конструктора и функцию purge, которая “очища-

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

экземпляры классов типа State для Mutable, сделать это будет совсем не трудно, ведь Mutable – это просто

118 | Глава 7: Функторы и монады: примеры

обёртка. С помощью этих экземпляров пользователь сможет комбинировать вычисления, которые связаны с

изменением памяти. Пока вроде всё хорошо, но обеспечиваем ли мы локальность изменения значений? Нам

важно, чтобы, один раз начав работать с памятью типа Mem, мы не смогли бы нигде воспользоваться этой па-

мятью после выполнения функции purge. Оказывается, что мы можем разрушить локальность. Посмотрите

на пример:

let mem = purge allocate

in

purge (read mem)

Мы возвращаем из функции purge ссылку на память и спокойно пользуемся ею в другой ветке Mutable-

вычислений. Можно ли этого избежать? Оказывается, что можно. Причём решение весьма элегантно. Мы

можем построить типы Mem и Mutable так, чтобы ссылке на память не удалось просочиться через функцию

purge. Для этого мы вернёмся к общему типу State c двумя параметрами. Причём первый первый параметр

мы прицепим и к Mem:

data

Mem

s a = ..

newtype Mutable s a = ..

new

:: a -> Mutable s (Mem s a)

write

:: Mem s a -> a -> Mutable s ()

read

:: Mem s a -> Mutable s a

Теперь при создании типы Mem и Mutable связаны общим параметром s. Посмотрим на тип функции purge

purge :: (forall s. Mutable s a) -> a

Она имеет необычный тип. Слово forall означает “для любых”. Это слово называют квантором всеобщ-

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

фиктивного состояния. Как дополнительный forall может нам помочь? Функция purge забывает тип фик-

тивного состояния s из типа Mutable, но в случае типа Mem, этот параметр продолжает своё путешествие по

программе в типе значения v :: Mem s a. По типу v компилятор может сказать, что существует такое s,

для которого значение v имеет смысл (правильно типизировано). Но оно не любое! Функцию purge с трю-

ком интересует не некоторый тип, а все возможные типы s, поэтому пример не пройдёт проверку типов.

Компилятор будет следить за “чистотой” наших обновлений.

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

FakeState? В Haskell специально для этого типа было сделано исключение. Мы возьмём его из воздуха. Это

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

ваться. Поскольку у нас нет конструктора Mutable мы никогда не сможем добраться до внутренней функции

типа State и извлечь состояние. Состояние скрыто за интерфейсом класса Monad и отбрасывается в функции

purge.