Я работаю над написанием схемы за 48 часов (у меня около 85 часов), и я дошел до части о добавлении переменных и назначений . В этой главе есть большой концептуальный скачок, и я бы хотел, чтобы он был сделан в два этапа с хорошим рефакторингом между ними, а не сразу к окончательному решению. Тем не мение…
Я заблудился с множеством различных классов , которые , кажется, служат той же цели: State
, ST
, IORef
, и MVar
. Первые три упомянуты в тексте, а последний, кажется, является предпочтительным ответом на многие вопросы StackOverflow о первых трех. Кажется, что все они несут состояние между последовательными вызовами.
Что такое каждый из них и чем они отличаются друг от друга?
В частности, эти предложения не имеют смысла:
Вместо этого мы используем функцию, называемую потоками состояний , позволяющую Haskell управлять агрегатным состоянием за нас. Это позволяет нам обрабатывать изменяемые переменные, как в любом другом языке программирования, используя функции для получения или установки переменных.
а также
Модуль IORef позволяет использовать переменные с состоянием в монаде ввода-вывода .
Все это type ENV = IORef [(String, IORef LispVal)]
сбивает с толку - почему второе IORef
? Что сломается, если я напишу type ENV = State [(String, LispVal)]
вместо этого?
MVar
должно быть предпочтительнееSTRef
?STRef
гарантирует, что только один поток может его изменить (и что не могут возникнуть другие типы ввода-вывода) - конечно, лучше, если мне не нужен одновременный доступ к изменяемому состоянию?Хорошо, начну с
IORef
.IORef
предоставляет значение, которое может изменяться в монаде ввода-вывода. Это просто ссылка на некоторые данные, и, как и в любой другой ссылке, есть функции, позволяющие изменять данные, на которые она ссылается. В Haskell все эти функции работают вIO
. Вы можете думать об этом как о базе данных, файле или другом внешнем хранилище данных - вы можете получать и устанавливать данные в нем, но для этого требуется выполнение ввода-вывода. Причина, по которой ввод-вывод вообще необходим, состоит в том, что Haskell чист ; компилятору нужен способ узнать, на какие данные указывает ссылка в любой момент времени (прочтите сообщение в блоге sigfpe «Вы могли изобрести монады» ).MVar
s - это в основном то же самое, что и 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
вычисления выполнялись в отдельном потоке с локальным хранилищем потока.источник
Другие сделали основные вещи, но чтобы ответить на прямой вопрос:
Лисп - это функциональный язык с изменяемым состоянием и лексической областью видимости. Представьте, что вы закрыли изменяемую переменную. Теперь у вас есть ссылка на эту переменную, которая висит внутри какой-то другой функции - скажем (в псевдокоде в стиле haskell)
(printIt, setIt) = let x = 5 in (\ () -> print x, \y -> set x y)
. Теперь у вас есть две функции: одна печатает x, а другая устанавливает его значение. Когда вы оцениваетеprintIt
, вы хотите для поиска по имени х в исходной среде , в которойprintIt
был определен, но вы хотите для запроса значения , что имя связывается в среде , в которойprintIt
находится называется (после того, какsetIt
можно было назвать любое число раз ).Для этого есть способы, помимо двух IORef, но вам определенно понадобится нечто большее, чем предложенный вами последний тип, который не позволяет изменять значения, с которыми связаны имена, в лексической области. Погуглите "задачу о фанаргах", чтобы найти много интересной предыстории.
источник