【发布时间】:2020-06-29 04:26:06
【问题描述】:
我正在尝试编写一个呈现一些 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、liftIO 等在TemplateM 中执行任意IO 操作。在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 内部暴露给外界?
【问题讨论】:
-
您无需担心执行任意 IO 操作,因为您的 monad 中有一个 IO。只是不要派生
MonadIO类(也不要导出TemplateM数据构造函数),你会没事的。 -
至于编写你的 monad 实例,尝试阐明你是如何被卡住的可能是有益的。你不能写这个实例对我来说并不奇怪——你的类型的结构本质上是一对,所以
Writer是你的灵感。但是WriterT将这对放在 inside monad 中,而不是像您所做的那样放在外面。此外,由于您打算从缓存中读取,而不仅仅是写入它,因此 Writer(又名配对)将不支持您需要的内容。也许你需要StateT? -
如果您对变形器还不满意,我建议您尝试自己实现一个状态单子
newtype State s a = State (s -> (a,s)),使用get :: State s s和put :: s -> State s (),以感受状态传递,然后回到这个,使用你从那个模式中学到的东西。 -
我同意卢克的观点。您的
TemplateM将缓存作为其成员之一,这意味着一元函数a -> TemplateM b返回 缓存作为输出。您正在寻找的是一个将缓存作为输入(并可能对其进行修改)的 monad,即StateT。