我正在尝试编写一个呈现一些 HTML 的 Monad,同时跟踪(和缓存)一些特定的函数调用。这是我尝试过的:
data TemplateM a = TemplateM
{ templateCache :: ![(Text, Text)]
, templateResult :: !(IO a)
}
下面是我打算如何使用它:
renderCached :: Text -> TemplateM Text
renderCached k =
-- lookup templateCache from the monadic context, if it lacks the key,
-- then fetch the key from an external data source (which is where the
-- "IO interior" comes from, and store it in templateCache (monadic context)
值得注意的是,我不希望通过 lift
在 TemplateM
中执行任意 IO
操作,liftIO
等。 TemplateM
中应该发生的唯一 IO
是通过 renderCached
函数从缓存中获取一些内容。
我能够为此定义 Functor
和 Applicative
实例,但完全无法使用 Monad
实例。这是我得到的结果:
instance Functor TemplateM where
{-# INLINE fmap #-}
fmap fn tmpl = tmpl{templateResult=fmap fn (templateResult tmpl)}
instance Applicative TemplateM where
{-# INLINE pure #-}
pure x = TemplateM
{ templateCache = []
, templateResult = pure x
}
{-# INLINE (<*>) #-}
fn <*> f =
let fnCache = templateCache fn
fnFunction = templateResult fn
fCache = templateCache f
fResult = templateResult f
in TemplateM { templateCache = fnCache <> fCache
, templateResult = fnFunction <*> fResult
}
有没有什么方法可以为此编写 Monad
实例而不将 IO
内部结构暴露给外界?
最佳答案
我已经在 ReaderT
之上制定了一个解决方案,但我真的想让我最初的想法发挥作用:
newtype TemplateM a = TemplateM { unTemplateM :: ReaderT (IORef [(Text, Text)]) IO a } deriving (Functor, Applicative, Monad)
renderCached :: Text -> TemplateM Text
renderCached k = TemplateM $ do
-- this is just dummy code. The actual cache lookup has not
-- been implemented, but the types align
v <- pure $ "rendered template for " <> k
cacheRef <- ask
atomicModifyIORef' cacheRef (\x -> ((k, v):x, ()))
pure v
runTemplateM :: [(Text, Text)]
-> TemplateM a
-> IO ([(Text, Text)], a)
runTemplateM initialCache x = do
initialCacheRef <- newIORef initialCache
(flip runReaderT) initialCacheRef $ do
res <- unTemplateM x
ref <- ask
finalCache <- readIORef ref
pure (finalCache, res)
关于haskell - 如何使用非 IO "exterior"构建 Monad,但 IO "interior"?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62631211/