【发布时间】:2012-06-19 02:34:49
【问题描述】:
我正在处理一个涉及打结的 Haskell 项目:我正在解析图形的序列化表示,其中每个节点都位于文件的某个偏移量处,并且可能通过其偏移量引用另一个节点。所以我需要在解析时建立一个从偏移量到节点的映射,我可以在 do rec 块中反馈给自己。
我有这个工作,并且有点-sorta-reasonably抽象成一个StateT-esque monad转换器:
{-# LANGUAGE DoRec, GeneralizedNewtypeDeriving #-}
import qualified Control.Monad.State as S
data Knot s = Knot { past :: s, future :: s }
newtype RecStateT s m a = RecStateT (S.StateT (Knot s) m a) deriving
( Alternative
, Applicative
, Functor
, Monad
, MonadCont
, MonadError e
, MonadFix
, MonadIO
, MonadPlus
, MonadReader r
, MonadTrans
, MonadWriter w )
runRecStateT :: RecStateT s m a -> Knot s -> m (a, Knot s)
runRecStateT (RecStateT st) = S.runStateT st
tie :: MonadFix m => RecStateT s m a -> s -> m (a, s)
tie m s = do
rec (a, Knot s' _) <- runRecStateT m (Knot s s')
return (a, s')
get :: Monad m => RecStateT s m (Knot s)
get = RecStateT S.get
put :: Monad m => s -> RecStateT s m ()
put s = RecStateT $ S.modify $ \ ~(Knot _ s') -> Knot s s'
tie 函数是魔法发生的地方:对runRecStateT 的调用会产生一个值和一个状态,我将其作为它自己的未来来提供。请注意,get 允许您读取过去和未来的状态,但 put 只允许您修改“现在”。
问题 1:总的来说,这似乎是实现这种打结模式的一种不错的方式吗?或者更好的是,是否有人对此实施了通用解决方案,而我在窥探 Hackage 时忽略了这一点?我在Cont monad 上打了一阵子,因为它看起来可能更优雅(参见 Dan Burton 的 similar post),但我就是搞不定。
完全主观的问题 2:我对调用代码的最终外观并不十分满意:
do
Knot past future <- get
let {- ... -} = past
{- ... -} = future
node = {- ... -}
put $ {- ... -}
return node
这里省略了实现细节,很明显,重要的一点是我必须获得past 和future 状态,模式匹配它们在一个 let 绑定中(或者明确地使前面的模式懒惰)提取我关心的任何内容,然后构建我的节点,更新我的状态并最终返回节点。似乎不必要地冗长,我特别不喜欢意外地使提取past 和future 状态的模式变得严格。那么,有人能想到更好的界面吗?
【问题讨论】:
-
即使他们正在构建相同的东西,您也可以为过去和未来的状态使用不同的类型。这样你就不会不小心把它们混在一起了。这就是我无论如何都会做的。
标签: haskell tying-the-knot monadfix