这个问题的解决方案是改变纯辅助函数。我们真的不希望它们是纯粹的,我们想泄露一个副作用——无论它们是否读取特定的数据。
假设我们有一个只使用衣服和硬币的纯函数:
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'彼此几乎相同。其中一个将其字段包装在TVars 中,而另一个则没有。我们可以进行修改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。它们的区别仅在于引用的类型(TVarvs. Identity)、我们使用什么函数从引用中获取值(readTVarvs 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 ms 上被参数化,它就是纯粹的。我们可以通过使用下面的MonadReadReffor 保存在Identity. idin与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的副作用。TVarSTMSTMTVar
instance MonadReadRef TVar STM where
readRef = readTVar