将结与国家monad绑在一起 [英] Tying the Knot with a State monad
问题描述
do rec
块中反馈给我自己。 我有这样的工作,并且有点不太合理地抽象成一个 StateT
-esque monad变换器:
{ - #LANGUAGE DoRec,GeneralizedNewtypeDivingiving# - }
将限定的Control.Monad.State导入为S
data结s = Knot {past :: s,future :: s}
newtype RecStateT sma = RecStateT(S.StateT(Knot s)ma)派生
(Alternative
,Applicative
,Functor
,Monad
,MonadCont
,MonadError e
,MonadFix
,MonadIO
,MonadPlus
,MonadReader r
,MonadTrans
,MonadWriter w)
runRecStateT :: RecStateT sma - >结点s - > m(a,Knot s)
runRecStateT(RecStateT st)= S.runStateT st
tie :: MonadFix m => RecStateT s m a - > s - > m(a,s)
tie ms = do
rec(a,Knot s'_)< - runRecStateT m(结s s')
return(a,s')
get :: Monad m => RecStateT s m(结)s
get = RecStateT S.get
put :: Monad m => s - > RecStateT s m()
put s = RecStateT $ S.modify $ \〜(Knot _ s') - >结$ s
runRecStateT
的调用会产生一个值和一个状态,我将它作为自己的将来。请注意, get
允许您从过去和未来状态读取,但 put
只允许您修改目前。
问题1 :这看起来像是一种体面的方式来实现这种结合模式吗?或者更好的是,是否有人实现了这个通用解决方案,当我通过Hackage进行窥探时,我忽略了这个解决方案?我对 Cont
monad打了个头,因为它看起来可能更优雅(参见从丹Burton),但我不能解决它。
完全主观的问题2 :我并不完全激动我的调用代码最终看起来的方式:
do
结过去未来< - 获得
让{ - ... - } =过去
{ - ... - } =未来
node = { - ... - }
put $ { - ... - }
返回节点
实现细节在这里省略了,显然,重要的一点是我必须获取过去
和未来
状态,将它们模式匹配到一个let绑定中(或者明确地让先前的模式懒惰)来提取我所关心的任何东西,然后构建我的节点,更新我的状态并最终返回节点。似乎不必要的冗长,我特别不喜欢这是多么容易让意外地提取过去
和未来
状态的模式严格。所以,任何人都可以想到一个更好的接口?
我一直在玩东西,我想我已经想出一些有趣的东西。我称之为先知monad,它提供了(除了Monad操作)两个基本操作:
参见:Monoid s =>先知s
send :: Monoid s => s - >先验s()
和运行操作:
runSeer :: Monoid s => Seer s - - > a
这个monad的工作方式是参见
允许一个seer查看所有内容,并且 send
允许一个seer将信息发送给所有其他seers供他们查看。每当任何一个seer执行参阅
操作时,他们都能够看到所有已发送的信息以及将要发送的所有信息。换句话说,在给定的运行过程中,无论您在何处或何时调用它,请参阅
总是会产生相同的结果。另一种说法是参见
就是你如何获得绑定结的工作参考。
这实际上与使用 fix
非常相似,除了所有子部分都是增量式和隐式式添加的,而不是显式地添加的。显然,在存在矛盾的情况下,先知不会正确地工作,并且需要足够的懒惰。例如,查看>> = send
可能会导致信息爆炸,并在时间循环中陷入困境。
一个愚蠢的例子:
import Control.Seer
将限定的Data.Map导入为M
import Data.Map(Map,(!))
bar :: Seer(Map Int Char)String
bar = do
m< - 请参阅
send(M .singleton 1 $ succ(m!2))
send(M.singleton 2'c')
return [m! 1,m! 2]
正如我所说,我刚刚在附近玩耍,所以我不知道这是否比你有什么更好的,或者如果它什么都好!但它很漂亮,而且相关,如果你的结状态是一个 Monoid
,那么它对你来说可能是有用的。公平的警告:我通过使用 Tardis
来建立 Seer
。
https://github.com/DanBurton/tardis/blob/master /Control/Seer.hs
I'm working on a Haskell project that involves tying a big knot: I'm parsing a serialized representation of a graph, where each node is at some offset into the file, and may reference another node by its offset. So I need to build up a map from offsets to nodes while parsing, which I can feed back to myself in a do rec
block.
I have this working, and kinda-sorta-reasonably abstracted into a StateT
-esque monad transformer:
{-# 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'
The tie
function is where the magic happens: the call to runRecStateT
produces a value and a state, which I feed it as its own future. Note that get
allows you to read from both the past and future states, but put
only allows you to modify the "present."
Question 1: Does this seem like a decent way to implement this knot-tying pattern in general? Or better still, has somebody implemented a general solution to this, that I overlooked when snooping through Hackage? I beat my head against the Cont
monad for a while, since it seemed possibly more elegant (see similar post from Dan Burton), but I just couldn't work it out.
Totally subjective Question 2: I'm not totally thrilled with the way my calling code ends up looking:
do
Knot past future <- get
let {- ... -} = past
{- ... -} = future
node = {- ... -}
put $ {- ... -}
return node
Implementation details here omitted, obviously, the important point being that I have to get the past
and future
state, pattern-match them inside a let binding (or explicitly make the previous pattern lazy) to extract whatever I care about, then build my node, update my state and finally return the node. Seems unnecessarily verbose, and I particularly dislike how easy it is to accidentally make the pattern that extracts the past
and future
states strict. So, can anybody think of a nicer interface?
I've been playing around with stuff, and I think I've come up with something... interesting. I call it the "Seer" monad, and it provides (aside from Monad operations) two primitive operations:
see :: Monoid s => Seer s s
send :: Monoid s => s -> Seer s ()
and a run operation:
runSeer :: Monoid s => Seer s a -> a
The way this monad works is that see
allows a seer to see everything, and send
allows a seer to "send" information to all other seers for them to see. Whenever any seer performs the see
operation, they are able to see all of the information that has been sent, and all of the information that will be sent. In other words, within a given run, see
will always produce the same result no matter where or when you call it. Another way of saying it is that see
is how you get a working reference to the "tied" knot.
This is actually very similar to just using fix
, except that all of the sub-parts are added incrementally and implicitly, rather than explicitly. Obviously, seers will not work correctly in the presence of a paradox, and sufficient laziness is required. For example, see >>= send
may cause an explosion of information, trapping you in a time loop.
A dumb example:
import Control.Seer
import qualified Data.Map as M
import Data.Map (Map, (!))
bar :: Seer (Map Int Char) String
bar = do
m <- see
send (M.singleton 1 $ succ (m ! 2))
send (M.singleton 2 'c')
return [m ! 1, m ! 2]
As I said, I've just been toying around, so I have no idea if this is any better than what you've got, or if it's any good at all! But it's nifty, and relevant, and if your "knot" state is a Monoid
, then it just might be useful to you. Fair warning: I built Seer
by using a Tardis
.
https://github.com/DanBurton/tardis/blob/master/Control/Seer.hs
这篇关于将结与国家monad绑在一起的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!