ekmett / machines

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

runHead function in examples looks shady #48

Open treeowl opened 9 years ago

treeowl commented 9 years ago
runHead :: (Functor f, Monad f) => MachineT f k b -> f b
runHead src = head <$> runT src

If I understand the situation properly, this should work just fine as applied in the example, but it seems a terrible example in and of itself. Aside from the partiality problem, my understanding is that runT accumulates a list whether the list is ever used or not. The right thing, presumably, is to build a machine that passes the first result through to the end.

treeowl commented 9 years ago

I think this works, but there's almost certainly a nicer way to do it:

runHead :: forall m k b . Monad m => MachineT m k b -> m (Maybe b)
runHead (MachineT m) = m >>= \case
                       Stop -> return Nothing
                       Yield o k -> runT_ k >> return (Just o)
                       Await _ _ e -> runHead e
ekmett commented 9 years ago

Makes sense to me.

glguy commented 9 years ago

Linking in taking 1 or construct (yield =<< await) would be a nice way to avoid driving into Step in an example.

treeowl commented 9 years ago

@glguy, I actually think the runner I proposed is sufficiently useful to add to the library as runT1. There are also sensible folding runners that run machines to completion and fold over the results.

glguy commented 9 years ago

We also have Data.Machine.Process.fold which can be linked to the end of a Process to fold all the results into one already.

treeowl commented 9 years ago

@glguy, D.M.P.fold and fold1 are what makes runT1 useful—it gives a sensible way to get the result of such machines. However, fold is a bit annoying in that it always produces exactly one result, but the type system doesn't know that, which brings in horrors like head or fromJust. Having a folding machine runner gets around that problem.