3

Toy URL Shortenerのコードを読んでいました。ただし、頭を悩ませることができない重要な部分があります。

次のコードがあります。

data URLShort = URLShort { state :: AcidState URLStore }

テスト目的で、私は自分のアプリで次のようなものを書きました:

data MyApp = MyApp { state :: Int }

次に、変更してコンパイルできます

main = warpDebug 3000 MyApp

main = warpDebug 3000 (MyApp 42)

そして、次のようにしてハンドラーで状態の読み取りを行うことができました

mystate <- fmap state getYesod 

記事にインスパイアされacid <- fmap state getYesodました。しかし、私は書き込みの方法を知りませんでした。

私もやってみました:

data MyApp = MyApp { state :: State Int Int }

しかし、私はこれで遠くまで行きませんでした。

私はAcidState単純な同様の例をいくつか実行するだけでどのように機能するかを理解しようとしていAcidStateました.すべてをメモリに保持しているので、同じことができるはずですか?

ここで何が起こっているのかについての一般的な説明と、おそらく私がどのようにポイントを逃しているかについて、非常に感謝しています。

4

1 に答える 1

7

データ型は完全に不変のAcidState a値ではありません。内部的に変更可能なデータへの参照が含まれています。この場合、Yesod-land に保存されるのは、このデータへの不変の参照です。状態を更新するとき、実際には基本データ型の値を更新するのではなく、それが指すメモリを更新します。

Haskell の世界のすべての値は不変です。ただし、Haskell の領域外の多くのものは不変ではありません。たとえば、 を実行するputStrLnと、端末は表示を変更して新しいコンテンツを表示します。putStrLnアクション自体は不変の純粋な値ですが、ミューテーションを伴うアクションを実行する方法を記述します。

ミューテーションを実行するアクションを生成する関数は他にもあります。を実行ref <- newIORef 0すると、変更可能なメモリ セルを作成するアクションを表す値が得られます。するとmodifyIORef ref (+1)、そのセルの値を だけインクリメントするアクションを説明する値が得られます1ref値は純粋な値であり、変更可能なセルへの単なる参照です。また、各部分はアクションのみを記述しているため、コードは純粋に機能的です。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

サイト カウンターの場合、実際にはTVarsではなく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

どうすればこれを修正できますか?

于 2012-04-04T12:49:05.833 に答える