new
::a -> Mutable( Mema)
read
:: Mema -> Mutablea
write
:: Mema ->a -> Mutable()
Мы предоставим пользователю лишь тип Mutableбез конструктора и функцию purge, которая “очища-
ет” значение от побочных эффектов и примитивные функции для работы с памятью. Также мы определим
экземпляры классов типа Stateдля Mutable, сделать это будет совсем не трудно, ведь Mutable– это просто
118 | Глава 7: Функторы и монады: примеры
обёртка. С помощью этих экземпляров пользователь сможет комбинировать вычисления, которые связаны с
изменением памяти. Пока вроде всё хорошо, но обеспечиваем ли мы локальность изменения значений? Нам
важно, чтобы, один раз начав работать с памятью типа Mem, мы не смогли бы нигде воспользоваться этой па-
мятью после выполнения функции purge. Оказывается, что мы можем разрушить локальность. Посмотрите
на пример:
letmem =purge allocate
in
purge (read mem)
Мы возвращаем из функции purge ссылку на память и спокойно пользуемся ею в другой ветке Mutable-
вычислений. Можно ли этого избежать? Оказывается, что можно. Причём решение весьма элегантно. Мы
можем построить типы Memи Mutableтак, чтобы ссылке на память не удалось просочиться через функцию
purge. Для этого мы вернёмся к общему типу Statec двумя параметрами. Причём первый первый параметр
мы прицепим и к Mem:
data
Mem
s a = ..
newtype Mutables a = ..
new
::a -> Mutables ( Mems a)
write
:: Mems a ->a -> Mutables ()
read
:: Mems a -> Mutables a
Теперь при создании типы Memи Mutableсвязаны общим параметром s. Посмотрим на тип функции purge
purge ::(forall s . Mutables a) ->a
Она имеет необычный тип. Слово forall означает “для любых”. Это слово называют квантором всеобщ-
ности. Этим мы говорим, что функция извлечения значения не может делать никаких предположений о типе
фиктивного состояния. Как дополнительный forall может нам помочь? Функция purge забывает тип фик-
тивного состояния s из типа Mutable, но в случае типа Mem, этот параметр продолжает своё путешествие по
программе в типе значения v :: Mems a. По типу v компилятор может сказать, что существует такое s,
для которого значение v имеет смысл (правильно типизировано). Но оно не любое! Функцию purge с трю-
ком интересует не некоторый тип, а все возможные типы s, поэтому пример не пройдёт проверку типов.
Компилятор будет следить за “чистотой” наших обновлений.
При таком подходе остаётся вопрос: откуда мы возьмём начальное значение, ведь теперь у нас нет типа
FakeState? В Haskell специально для этого типа было сделано исключение. Мы возьмём его из воздуха. Это
чисто фиктивный параметр, нам главное, что он скрыт от пользователя, и он нигде не может им воспользо-
ваться. Поскольку у нас нет конструктора Mutableмы никогда не сможем добраться до внутренней функции
типа Stateи извлечь состояние. Состояние скрыто за интерфейсом класса Monadи отбрасывается в функции
purge.
Тип ST
Выше я пользовался вымышленными типами для упрощения объяснений, на самом деле в Haskell за об-
новление значений отвечает тип ST(сокращение от state transformer). Он живёт в модуле Control.Monad.ST.
Из документации видно, что у него два параметра, и нет конструкторов:
data STs a
Это наш тип Mutable, теперь посмотрим на тип Mem. Он называется ST-ссылкой и определён в модуле
Data.STRef(сокращение от ST reference). Посмотрим на основные функции:
newSTRef
::a -> STs ( STRefs a)
readSTRef
:: STRefs a -> STs a
writeSTRef
:: STRefs a ->a -> STs ()
Такие функции иногда называют смышлёными конструкторами (smart constructors) они позволяют строить
значение, но скрывают от пользователя реализацию за счёт скрытия конструкторов типа (модуль экспорти-
рует лишь имя типа STRef).
Для иллюстрации этих функций реализуем одну вспомогательную функцию из модуля Data.STRef, функ-
цию обновления значения по ссылке:
modifySTRef :: STRefs a ->(a ->a) -> STs ()
modifySTRef ref f =writeSTRef .f =<<readSTRef ref
Мы воспользовались тем, что STявляется экземпляром Monad. Также как и для Stateдля STопределены
Читать дальше