52700.fb2
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.