この問題の解決策は、純粋なヘルパー関数を変更することです。それらが純粋であることを本当に望んでいるわけではありません。特定のデータを読み取るかどうかに関係なく、単一の副作用を漏らしたいのです。
衣服とコインのみを使用する純粋な関数があるとします。
moreVanityThanWealth :: IntMap Clothing -> IntMap Coins -> Bool
moreVanityThanWealth clothing coins = ...
通常、関数が衣服やコインなどにのみ関心があることを知っておくと便利ですが、あなたの場合、この知識は無関係であり、頭痛の種になっています。この詳細は意図的に忘れてしまいます。mb14 の提案に従えばMudData'
、次のようなピュア全体をヘルパー関数に渡します。
data MudData' = MudData' { _armorTbl :: IntMap Armor
, _clothingTbl :: IntMap Clothing
, _coinsTbl :: IntMap Coins
moreVanityThanWealth :: MudData' -> Bool
moreVanityThanWealth md =
let clothing = _clothingTbl md
coins = _coinsTbl md
in ...
MudData
とMudData'
ほぼ同じです。それらの 1 つはそのフィールドをTVar
s でラップし、もう 1 つはラップしません。フィールドをラップするためMudData
の追加の型パラメーター ( kind の) を受け取るように変更できます。レンズと密接に関連していますが、ライブラリのサポートはあまりありません。このパターンをModelと呼びます。* -> *
MudData
(* -> *) -> *
data MudData f = MudData { _armorTbl :: f (IntMap Armor)
, _clothingTbl :: f (IntMap Clothing)
, _coinsTbl :: f (IntMap Coins)
MudData
でオリジナルを復元できMudData TVar
ます。Identity
フィールドを, でラップすることにより、純粋なバージョンを再作成できますnewtype Identity a = Identity {runIdentity :: a}
。に関してはMudData Identity
、関数は次のように記述されます。
moreVanityThanWealth :: MudData Identity -> Bool
moreVanityThanWealth md =
let clothing = runIdentity . _clothingTbl $ md
coins = runIdentity . _coinsTbl $ md
in ...
のどの部分を使用したかを忘れることに成功しましたMudData
が、必要なロックの粒度がありません。副作用として、忘れていたものを正確に回復する必要があります。STM
ヘルパーのバージョンを書くと、次のようになります
moreVanityThanWealth :: MudData TVar -> STM Bool
moreVanityThanWealth md =
do
clothing <- readTVar . _clothingTbl $ md
coins <- readTVar . _coinsTbl $ md
return ...
のこのSTM
バージョンMudData TVar
は、先ほど書いた の純粋なバージョンとほぼ同じですMudData Identity
。それらは、参照のタイプ ( TVar
vs. Identity
)、参照から値を取得するために使用する関数 ( readTVar
vs runIdentity
)、および結果がどのように返されるか (STM
またはプレーン値として) だけが異なります。同じ関数を使用して両方を提供できるとよいでしょう。2 つの機能の共通点を抽出します。そのために、ある型の参照を読み取ることができる s の型クラスMonadReadRef r m
を導入します。は参照の型、 は参照から値を取得する関数、は結果を返す方法です。以下は、Monad
r
readRef
m
MonadReadRef
MonadRef
ref-fdからのクラス。
{-# LANGUAGE FunctionalDependencies #-}
class Monad m => MonadReadRef r m | m -> r where
readRef :: r a -> m a
コードがすべての でパラメータ化されている限り、コードMonadReadRef r m
は純粋です。MonadReadRef
これは、 に保持されている通常の値の次のインスタンスで実行することで確認できますIdentity
。インid
はreadRef = id
と同じreturn . runIdentity
です。
instance MonadReadRef Identity Identity where
readRef = id
moreVanityThanWealth
という形で書き直しますMonadReadRef
。
moreVanityThanWealth :: MonadReadRef r m => MudData r -> m Bool
moreVanityThanWealth md =
do
clothing <- readRef . _clothingTbl $ md
coins <- readRef . _coinsTbl $ md
return ...
にs のMonadReadRef
インスタンスを追加すると、これらの「純粋な」計算を で使用できますが、 s が読み込まれたことによる副作用が漏れます。TVar
STM
STM
TVar
instance MonadReadRef TVar STM where
readRef = readTVar