我正在处理一个涉及打结的 Haskell 项目:我正在解析图形的序列化表示,其中每个节点都位于文件的某个偏移量处,并且可能通过其偏移量引用另一个节点。do rec
所以我需要在解析时建立一个从偏移量到节点的映射,我可以在一个块中反馈给自己。
我有这个工作,并且有点合理地抽象成一个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的类似帖子),但我就是无法解决。
完全主观的问题 2:我对我的调用代码最终看起来的方式并不完全兴奋:
do
Knot past future <- get
let {- ... -} = past
{- ... -} = future
node = {- ... -}
put $ {- ... -}
return node
显然,这里省略了实现细节,重要的一点是我必须在 let 绑定past
中获取和future
状态,模式匹配它们(或显式使先前的模式变得惰性)以提取我关心的任何内容,然后构建我的节点,更新我的状态,最后返回节点。似乎不必要地冗长,而且我特别不喜欢意外地使提取and状态的模式变得严格是多么容易。那么,有人能想到更好的界面吗?past
future