ekmett / machines

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

runT is silly; accumulating and simpler version desired #50

Open treeowl opened 9 years ago

treeowl commented 9 years ago

It may be too late to replace it, but runT doesn't seem to be good for much other than debugging. I suspect the most sensible general purpose version is

runTS :: (Monad m, Semigroup b) => MachineT m k b -> m (Maybe b)

runT could be written in terms of runTS in a few different ways, either first mapping (:| []) over it and then reversing the result, or doing some trick or other with Endo; then at the end Maybe (NonEmpty a) gets turned into [a].

runTS itself can be defined in terms of fold1 and an even more basic machine runner that runs a machine until it produces an output, then stop it. Return that output; if the machine stopped with no output, return Nothing.

treeowl commented 9 years ago

Here's the gist of what I mean. You may be able to find a better approach.

-- Run a model until it either produces an
-- output or stops. If the model produces output,
-- both that output and the stepped model are produced.
runTFirstOutput :: Monad m => MachineT m k o -> m (Maybe (o, MachineT m k o))
runTFirstOutput (MachineT m) = do
  res <- m
  case res of
    Stop -> return Nothing
    Yield o r -> return $ Just (o, r)
    Await _ _ r -> runTFirstOutput r

-- Run a model until it either produces output or stops.
-- If the model produces output, only the output is revealed;
-- the model cannot be resumed.
runTFirst :: Monad m => MachineT m k o -> m (Maybe o)
runTFirst = liftM (fmap fst) . runTFirstOutput

-- Run a model producing elements in some semigroup; accumulate
-- them.
runTS :: (Semigroup o, Monad m) => MachineT m k o -> m (Maybe o)
runTS m = runTFirst (m ~> fold1 (flip (<>)))

-- This could also be written to use endomorphism stuff. Whatever.
runT :: Monad m => MachineT m k o -> m [o]
runT m = runTS (fmap (: []) m) >>= maybe (return []) (return . reverse)
treeowl commented 9 years ago

Actually, there should probably be a separate fold operation in between, specialized to deal with folding a semigroup. It should also be possible to write a more foldl-style version of runT, which I didn't even think about, but it's a little uglier.

treeowl commented 9 years ago

OK, this seems a bit better developed:

-- Convert a MachineT to a SourceT by blocking off its input.
machineTtoSourceT :: forall m k o . Monad m => MachineT m k o -> SourceT m o
machineTtoSourceT m = construct $ go m
  where
    go mach = do
           res <- lift $ runMachineT mach
           case res of
             Stop -> stop
             Yield o r -> yield o >> go r
             Await _ _ r -> go r

-- The exposed polymorphism of SourceT sometimes leads to problems
-- with impredicativity.This version uses Void instead.
type AltSourceT m = MachineT m (Const Void)

machineTtoAltSourceT :: forall m k o . Monad m => MachineT m k o -> AltSourceT m o
machineTtoAltSourceT = machineTtoSourceT

-- Every SourceT is automatically an AltSourceT
sourceTtoAltSourceT :: SourceT m o -> AltSourceT m o
sourceTtoAltSourceT m = m

-- An AltSourceT can't possibly await, so it can be converted to a SourceT
altSourceTtoSourceT :: Monad m => AltSourceT m o -> SourceT m o
altSourceTtoSourceT m = construct $ go m
   where
     go mach = do
      res <- lift $ runMachineT mach
      case res of
        Stop -> stop
        Yield o r -> yield o >> go r
        Await _ (Const ab) _ -> absurd ab   -- Unreachable

-- Run an AltSourceT until it either produces an
-- output or stops. If the model produces output,
-- both that output and the stepped model are produced.
runAltSourceTFirstOutput :: forall m o . Monad m => AltSourceT m o -> m (Maybe (o, AltSourceT m o))
runAltSourceTFirstOutput m = do
  res <- runMachineT m
  case res of
    Stop -> return Nothing
    Yield o r -> return $ Just (o, r)
    Await _ (Const ab) _ -> absurd ab  -- Unreachable

-- Run a model without input until it either produces an
-- output or stops. If the model produces output,
-- both that output and the stepped model are produced.
-- The stepped machine does not accept input. Note that
-- it is impossible to use SourceT here, at least directly,
-- as that would be impredicative.
runTFirstOutput' :: Monad m => MachineT m k o -> m (Maybe (o, AltSourceT m o))
runTFirstOutput' = runAltSourceTFirstOutput . machineTtoAltSourceT

-- Run a model without input until it either produces an
-- output or stops. If the model produces output,
-- both that output and the stepped model are produced.
-- The stepped machine may accept new input, which may or
-- may not make sense.
runTFirstOutput :: Monad m => MachineT m k o -> m (Maybe (o, MachineT m k o))
runTFirstOutput m = do
  res <- runMachineT m
  case res of
    Stop -> return Nothing
    Yield o r -> return $ Just (o, r)
    Await _ _ r -> runTFirstOutput r

-- Run a model until it either produces output or stops.
-- If the model produces output, only the output is revealed;
-- the model cannot be resumed.
runTFirst :: Monad m => MachineT m k o -> m (Maybe o)
runTFirst = liftM (fmap fst) . runTFirstOutput'

-- Run a model producing elements in some semigroup; accumulate
-- them.
runTS :: (Semigroup o, Monad m) => MachineT m k o -> m (Maybe o)
runTS m = runTFirst (m ~> fold1 (flip (<>)))

runT :: Monad m => MachineT m k o -> m [o]
runT m = runTS (fmap (: []) m) >>= maybe (return []) (return . reverse)
treeowl commented 9 years ago

Bah. The pretty idea of converting a MachineT to a SourceT and then running the SourceT doesn't seem to optimize away as I hoped. The simpler original proposal is therefore more practical. I'm not sure if the flip should be there or not. I'm leaning toward not.