Тип данных AcidState a
не является неизменяемым значением до конца; он содержит внутренние ссылки на изменяемые данные. То, что хранится в земле Йесод, в данном случае является просто неизменяемой ссылкой на эти данные. Когда вы обновляете состояние, вы фактически обновляете не значение базового типа данных, а память, на которую оно указывает.
Каждое значение в мире Haskell является неизменным. Однако многие вещи за пределами Царства Хаскелла не являются неизменными; например, когда вы делаете putStrLn
, терминал меняет свой дисплей, чтобы показать новый контент. Само действие putStrLn
представляет собой неизменяемое чистое значение, но оно описывает, как выполнить действие, связанное с изменением.
Есть и другие функции, которые также производят действия, выполняющие мутации; если вы делаете ref <- newIORef 0
, вы получаете значение, описывающее действие, которое создает изменяемую ячейку памяти. Если вы затем сделаете modifyIORef ref (+1)
, вы получите значение, описывающее действие, которое увеличивает значение в этой ячейке на 1
. Значение ref
является чистым значением, это просто ссылка на изменяемую ячейку. Код также чисто функциональный, поскольку каждая часть описывает только действие; ничто не может быть изменено в программе Haskell.
Вот как AcidState
реализует свое состояние: используя систему, которая управляет состоянием за пределами мира Haskell. Это не так плохо, как полная изменчивость, как в таких языках, как C, потому что в Haskell вы можете управлять изменчивостью с помощью монад. Использование AcidState
совершенно безопасно и, насколько мне известно, не предполагает использования unsafePerformIO
.
В этом случае с AcidState
вы используете openAcidState emptyStore
в монаде IO
для создания нового кислотного состояния (эта строка является значением, описывающим действие ввода-вывода, которое открывает новое кислотное состояние). Вы используете createCheckpointAndClose
для безопасного сохранения кислотного состояния на диск. Наконец, вы используете функцию update'
для изменения содержимого кислотного состояния.
Чтобы создать «маленькое состояние» самостоятельно, используя IORef
s (самая простая форма изменяемого состояния, за исключением, может быть, монады ST
), вы сначала добавляете такое поле к базовому типу данных:
data VisitorCounter = VisitorCounter { visitorCounter :: IORef Int }
Затем вы делаете:
main = do
counter <- newIORef 0
warpDebug 3000 (VisitorCounter counter)
В обработчике вы можете изменить счетчик следующим образом:
counter <- fmap visitorCounter getYesod
modifyIORef counter (+1)
count <- readIORef counter
-- ... display the count or something
Обратите внимание на симметрию к AcidState
.
Для счетчика сайтов я бы рекомендовал использовать TVar
s вместо IORef
s из-за возможности одновременного изменения переменной несколькими клиентами. Однако интерфейс TVar
s очень похож.
Дополнительный вопрос от автора вопроса?
Я поместил { visitorCounter :: TVar Int }
в свой базовый тип и следующий код в обработчик:
counter <- fmap visitorCounter getYesod
count <- readTVar counter
Первая строка компилируется нормально, но вторая выдает эту ошибку:
Couldn't match expected type `GHandler sub0 Middleware t0'
with actual type `STM a0'
In the return type of a call of `readTVar'
In a stmt of a 'do' expression: count <- readTVar counter
Как я могу это исправить?
person
dflemstr
schedule
04.04.2012