it-roy-ru.com

Разница между State, ST, IORef и MVar

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

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

Каковы все эти и чем они отличаются друг от друга?


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

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

а также

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

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

85
John F. Miller

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

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

  • get
  • положил

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

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

Монада ST и STRefs

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

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

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

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

Основной API:

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

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

IORef: STRefs в IO

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

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

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

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

112
Don Stewart

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

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

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

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

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

36
John L

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

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

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

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

18
sclv