ekmett / machines

Networks of composable stream transducers
Other
339 stars 46 forks source link

Make it easy to write stateful machines #59

Closed treeowl closed 9 years ago

treeowl commented 9 years ago

I think something like this should work, with a saner name, but I haven't tested it. I don't know if something like this is available for plans; PlanT confuses the heck out of me.

fob :: forall m k o s . Monad m => s -> MachineT (StateT s m) k o -> MachineT m k o
fob s (MachineT m) = MachineT $ do
  (stp, s') <- runStateT m s
  case stp of
    Stop -> return Stop
    Yield o m' -> return $ Yield o (fob s' m')
    Await f k q -> return $ Await (fob s' . f) k (fob s' q)
treeowl commented 9 years ago

Note: the type of fitM suggests it could do this job, but flip evalStateT s is not a monad morphism; the state will never change.

YoEight commented 9 years ago

Somehow I think that kind of thing should happen at the PlanT level. PlanT supports MonadState if my memory serves me right.

treeowl commented 9 years ago

This might go well with a function of type Monad m => (i -> ProcessT m i o) -> ProcessT m i o, which I've used previously to initialize Attoparsec parsers.

@YoEight, PlanT k o m is only MonadState if m is. I'm not actually sure if the conversion I'm talking about even makes sense for a PlanT, except in the process of converting it to a MachineT. Also remember that not every MachineT comes from a PlanT. They can be built with <> and ~> and all sorts of other fun things.

YoEight commented 9 years ago

Are you asking for the same kind of stateful combinators pipes has (or used to have) for instance ?

treeowl commented 9 years ago

@YoEight, I know pretty much nothing about pipes. It's possible to make stateful machines by doing things like

blah = construct . flip runStateT s0 $ forever go
  where
     go = do
       x <- lift await
       blah blah blah
       lift (yield foo)

or else manually with

blah = construct (go s0)
  where
    go s = do
      x <- await
      whatever
      yield foo
      go s1

The looping must be done within the PlanT itself. With the function I proposed, you can instead write things like

blah = fob s0 $ boom ~> (repeatedly $ do
  x <- await
  whatever
  yield foo)
treeowl commented 9 years ago

@YoEight, someone pointed me toward Pipes.Lift.runStateP. This is actually more like execStateP.

ekmett commented 9 years ago

Interesting. I presume we wind up more execStateP-like because we have no final result type to put the answer state into?

treeowl commented 9 years ago

Correct. We might be able to play games with Either or something, but it's not obvious to me what makes sense there, so I'm not proposing anything. I'm fairly sure the execStateP analogue (execStateM?) is sane. We could also add runReaderM e = fitM (flip runReaderT e) for completeness. I pondered whether there's anything useful to do with ContT, but reached no definite conclusion. On Aug 24, 2015 12:46 PM, "Edward Kmett" notifications@github.com wrote:

Interesting. I presume we wind up more execStateP-like because we have no final result type to put the answer state into?

— Reply to this email directly or view it on GitHub https://github.com/ekmett/machines/issues/59#issuecomment-134296062.

treeowl commented 9 years ago

We can also port some exception handling from pipes. I think this is probably right:

catchExcept :: Monad m
               => MachineT (ExceptT e m) k o
               -> (e -> MachineT (ExceptT e m) k o)
               -> MachineT (ExceptT e m) k o
catchExcept m c =
    MachineT $ do
      step <- E.catchE (runMachineT m) (\e -> runMachineT (catchExcept (c e) c))
      case step of
        Stop -> return Stop
        Yield o m' -> return $ Yield o (catchExcept m' c)
        Await f k m' -> return $ Await (flip catchExcept c . f) k (catchExcept m' c)

I have the feeling there's some simpler way to handle this.

treeowl commented 9 years ago

Added in #61