将结与国家monad绑在一起 [英] Tying the Knot with a State monad

查看:137
本文介绍了将结与国家monad绑在一起的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在研究一个Haskell项目,它涉及到一个大结:我解析一个图的序列化表示,其中每个节点在文件中有一些偏移量,并且可以通过偏移量引用另一个节点。所以我需要在解析时建立从偏移到节点的映射,我可以在 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

tie 函数是魔术发生的地方:对 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆