データ型は完全に不変のAcidState a
値ではありません。内部的に変更可能なデータへの参照が含まれています。この場合、Yesod-land に保存されるのは、このデータへの不変の参照です。状態を更新するとき、実際には基本データ型の値を更新するのではなく、それが指すメモリを更新します。
Haskell の世界のすべての値は不変です。ただし、Haskell の領域外の多くのものは不変ではありません。たとえば、 を実行するputStrLn
と、端末は表示を変更して新しいコンテンツを表示します。putStrLn
アクション自体は不変の純粋な値ですが、ミューテーションを伴うアクションを実行する方法を記述します。
ミューテーションを実行するアクションを生成する関数は他にもあります。を実行ref <- newIORef 0
すると、変更可能なメモリ セルを作成するアクションを表す値が得られます。するとmodifyIORef ref (+1)
、そのセルの値を だけインクリメントするアクションを説明する値が得られます1
。ref
値は純粋な値であり、変更可能なセルへの単なる参照です。また、各部分はアクションのみを記述しているため、コードは純粋に機能的です。Haskell プログラム内では何も変更できません。
これはAcidState
、Haskell の世界の外で状態を管理するシステムを使用して、その状態を実装する方法です。Haskell では、モナドの力で可変性を制御できるため、これは C などの言語のように完全な可変性を持つ「ほど悪い」ものではありません。使用は完全に安全であり、私が知る限りAcidState
の使用は含まれません.unsafePerformIO
このAcidState
場合、モナドで を使用openAcidState emptyStore
してIO
、新しい酸状態を作成します (その行は、新しい酸状態を開く IO アクションを記述する値です)。createCheckpointAndClose
オプションで、酸性状態をディスクに安全に保存するために使用します。最後に、update'
関数を使用して酸性状態の内容を変異させます。
sを使用して自分で「小さな状態」を作成するにはIORef
(おそらくモナドを除いて、可変状態の最も単純な形式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ではなくs を使用することをお勧めしIORef
ます。これは、複数のクライアントが変数を同時に変更する可能性があるためです。ただし、sへのインターフェイスTVar
は非常に似ています。
質問の作成者からのフォローアップの質問?
基礎型に配置{ visitorCounter :: TVar Int }
し、ハンドラーに次のコードを配置しました。
counter <- fmap visitorCounter getYesod
count <- readTVar counter
最初の行は正常にコンパイルされますが、2 番目の行は次のエラーをスローします。
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
どうすればこれを修正できますか?