这个问题的解决方案是改变纯辅助函数。我们真的不希望它们是纯粹的,我们想泄露一个副作用——无论它们是否读取特定的数据。
假设我们有一个只使用衣服和硬币的纯函数:
moreVanityThanWealth :: IntMap Clothing -> IntMap Coins -> Bool
moreVanityThanWealth clothing coins = ...
很高兴知道一个函数只关心例如衣服和硬币,但在你的情况下,这些知识是无关紧要的,只会让人头疼。我们会故意忘记这个细节。如果我们遵循 mb14 的建议,我们会将MudData'
如下所示的整个 pure 传递给辅助函数。
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'
彼此几乎相同。其中一个将其字段包装在TVar
s 中,而另一个则没有。我们可以进行修改MudData
,以便它需要一个额外的类型参数( kind * -> *
)来包装字段。MudData
将具有稍微不寻常的 kind (* -> *) -> *
,它与镜头密切相关,但没有太多的库支持。我称这种模式为Model。
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
或作为普通值)。如果可以使用相同的功能来提供两者,那就太好了。我们将提取两个函数之间的共同点。为此,我们将为 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
s 上被参数化,它就是纯粹的。我们可以通过使用下面的MonadReadRef
for 保存在Identity
. id
in与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