IO monad 中的明显冗余调用?

问题描述

这里是摘自 Haskell GPipe 项目的一段代码(由我自己评论,用“真的吗?”保存该行)。在 memoize 函数中,我不明白为什么它的作者第二次调用 getter 来缓存新计算的值。对我来说似乎没有必要,它可以在没有明显不良后果的情况下删除(至少,我的一个中型项目在没有它的情况下仍然可以工作)。

{- | A map (SN stands for stable name) to cache the results 'a' of computations 'm a'.
The type 'm' ends up being constrained to 'Monadio m' in the varIoUs functions using it.
-}
newtype SNMap m a = SNMap (HT.BasicHashTable (StableName (m a)) a)

newSNMap :: IO (SNMap m a)
newSNMap = SNMap <$> HT.new

memoize :: Monadio m
    => m (SNMap m a) -- ^ A "IO call" to retrieve our cache.
    -> m a -- ^ The "IO call" to execute and cache the result.
    -> m a -- ^ The result being naturally also returned.
memoize getter m = do
    s <- liftIO $ makeStableName $! m -- Does forcing the evaluation make sense here (since we try to avoid it...)?
    SNMap h <- getter
    x <- liftIO $ HT.lookup h s
    case x of
        Just a -> return a
        nothing -> do
            a <- m
            SNMap h' <- getter -- Need to redo because of scope. <- Really?
            liftIO $ HT.insert h' s a
            return a

解决方法

我明白了。使用的范围术语与 Haskell 的“do”范围无关。很简单,计算可以在评估时递归更新缓存(如在同一模块中的 scopedM 函数......)。回想起来很明显。