Разница между состоянием, ST, IORef и MVar

91

Я работаю над написанием схемы за 48 часов (у меня около 85 часов), и я дошел до части о добавлении переменных и назначений . В этой главе есть большой концептуальный скачок, и я бы хотел, чтобы он был сделан в два этапа с хорошим рефакторингом между ними, а не сразу к окончательному решению. Тем не мение…

Я заблудился с множеством различных классов , которые , кажется, служат той же цели: State, ST, IORef, и MVar. Первые три упомянуты в тексте, а последний, кажется, является предпочтительным ответом на многие вопросы StackOverflow о первых трех. Кажется, что все они несут состояние между последовательными вызовами.

Что такое каждый из них и чем они отличаются друг от друга?


В частности, эти предложения не имеют смысла:

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

а также

Модуль IORef позволяет использовать переменные с состоянием в монаде ввода-вывода .

Все это type ENV = IORef [(String, IORef LispVal)]сбивает с толку - почему второе IORef? Что сломается, если я напишу type ENV = State [(String, LispVal)]вместо этого?

Джон Ф. Миллер
источник

Ответы:

119

Государственная монада: модель изменчивого состояния

Монада состояния - это чисто функциональная среда для программ с состоянием с простым API:

  • получить
  • ставить

Документация в пакете mtl .

Монада состояния обычно используется, когда требуется состояние в одном потоке управления. На самом деле он не использует изменяемое состояние в своей реализации. Вместо этого программа параметризуется значением состояния (т. Е. Состояние является дополнительным параметром для всех вычислений). Состояние кажется измененным только в одном потоке (и не может быть разделено между потоками).

Монада ST и STRefs

Монада ST является родственником монады IO.

Это позволяет произвольное изменяемое состояние , реализованное как фактическая изменяемая память на машине. API безопасен в программах без побочных эффектов, поскольку параметр типа rank-2 предотвращает выход значений, зависящих от изменяемого состояния, из локальной области.

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

Обычно используется для изменяемых массивов и других структур данных, которые видоизменяются, а затем замораживаются. Это также очень эффективно, поскольку изменяемое состояние "аппаратно ускорено".

Первичный API:

  • Control.Monad.ST
  • runST - начать новое вычисление эффекта памяти.
  • И STRefs : указатели на (локальные) изменяемые ячейки.
  • Массивы на основе ST (например, векторные) также распространены.

Считайте его менее опасным братом монады ввода-вывода. Или IO, где можно только читать и писать в память.

IORef: STRefs в IO

Это STRef (см. Выше) в монаде IO. У них нет таких же гарантий безопасности, как у STRefs в отношении местности.

MVars: IORefs с блокировками

Подобно STRefs или IORefs, но с прикрепленной блокировкой для безопасного одновременного доступа из нескольких потоков. IORefs и STRefs безопасны только в многопоточной настройке при использованииatomicModifyIORef (атомарная операция сравнения и замены). MVars - это более общий механизм безопасного обмена изменяемым состоянием.

Как правило, в Haskell используйте MVars или TVars (изменяемые ячейки на основе STM) вместо STRef или IORef.

Дон Стюарт
источник
3
Что означает M в MVars и T в TVars? Я предполагаю «Мутабельный», «Транзакционный». Интересно, как ST означает State Thread.
CMCDragonkai
10
Почему вы говорите, что это MVarдолжно быть предпочтительнее STRef? STRefгарантирует, что только один поток может его изменить (и что не могут возникнуть другие типы ввода-вывода) - конечно, лучше, если мне не нужен одновременный доступ к изменяемому состоянию?
Бенджамин Ходжсон
@CMCDragonkai Я всегда предполагал, что M означает мьютекс, но нигде не могу найти его задокументированного.
Эндрю Таддеус Мартин
37

Хорошо, начну с IORef. IORefпредоставляет значение, которое может изменяться в монаде ввода-вывода. Это просто ссылка на некоторые данные, и, как и в любой другой ссылке, есть функции, позволяющие изменять данные, на которые она ссылается. В Haskell все эти функции работают в IO. Вы можете думать об этом как о базе данных, файле или другом внешнем хранилище данных - вы можете получать и устанавливать данные в нем, но для этого требуется выполнение ввода-вывода. Причина, по которой ввод-вывод вообще необходим, состоит в том, что Haskell чист ; компилятору нужен способ узнать, на какие данные указывает ссылка в любой момент времени (прочтите сообщение в блоге sigfpe «Вы могли изобрести монады» ).

MVars - это в основном то же самое, что и IORef, за исключением двух очень важных отличий. MVarявляется примитивом параллелизма, поэтому он предназначен для доступа из нескольких потоков. Второе отличие заключается в том, что это MVarполе, которое может быть полным или пустым. Итак, если у IORef Intвсегда есть Int(или внизу), у А MVar Intможет быть Intили он может быть пустым. Если поток пытается прочитать значение из пустого MVar, он будет блокироваться до тех пор, пока не MVarбудет заполнено (другим потоком). По сути, это MVar aэквивалентно IORef (Maybe a)с дополнительной семантикой, которая полезна для параллелизма.

State- это монада, которая обеспечивает изменяемое состояние, не обязательно с вводом-выводом. Фактически, это особенно полезно для чистых вычислений. Если у вас есть алгоритм, который использует состояние, но не IOиспользуетState монада часто оказывается элегантным решением.

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

STчто-то немного другое. Основная структура данных ST- STRefэто объект, который похож на объект, IORefно с другой монадой. STСистема типа монады использует обман (в «государственных нитях» Документы упоминают) , чтобы гарантировать , что изменяемые данные не могут избежать монад; то есть, когда вы запускаете вычисление ST, вы получаете чистый результат. Причина, по которой ST интересен, заключается в том, что это примитивная монада, подобная IO, позволяющая вычислениям выполнять низкоуровневые манипуляции с байтовыми массивами и указателями. Это означает, что STможно обеспечить чистый интерфейс при использовании низкоуровневых операций с изменяемыми данными, то есть очень быстро. С точки зрения программы, это как если бы STвычисления выполнялись в отдельном потоке с локальным хранилищем потока.

Джон Л
источник
17

Другие сделали основные вещи, но чтобы ответить на прямой вопрос:

Все это ENV = IORef [(String, IORef LispVal)] сбивает с толку тип линии . Почему второй IORef? Что сломается, если я сделаю это type ENV = State [(String, LispVal)]вместо этого?

Лисп - это функциональный язык с изменяемым состоянием и лексической областью видимости. Представьте, что вы закрыли изменяемую переменную. Теперь у вас есть ссылка на эту переменную, которая висит внутри какой-то другой функции - скажем (в псевдокоде в стиле haskell) (printIt, setIt) = let x = 5 in (\ () -> print x, \y -> set x y). Теперь у вас есть две функции: одна печатает x, а другая устанавливает его значение. Когда вы оцениваете printIt, вы хотите для поиска по имени х в исходной среде , в которой printItбыл определен, но вы хотите для запроса значения , что имя связывается в среде , в которой printItнаходится называется (после того, как setItможно было назвать любое число раз ).

Для этого есть способы, помимо двух IORef, но вам определенно понадобится нечто большее, чем предложенный вами последний тип, который не позволяет изменять значения, с которыми связаны имена, в лексической области. Погуглите "задачу о фанаргах", чтобы найти много интересной предыстории.

sclv
источник