I'm trying to understand monads and arrows by just programming some things that I need that also feel like they might involve what people are talking about when I read Haskell tutorials, and I've come up with an example that I can't quite wrap my head around:
newtype Stateful a b = Stateful { runStateful :: a -> (b, Stateful a b) }
stateful :: (a -> (b, Stateful a b)) -> Stateful a b
stateful = Stateful
-- executes a stateful computation, returning its result
evalStateful :: Stateful a b -> a -> b
evalStateful f x = fst $ runStateful f x
-- execStateful :: ??
execStateful = undefined
-- converts a stateful function to Stateful
close :: (s -> a -> (b, s)) -> s -> Stateful a b
close f s = stateful $ \x -> let (y, s') = f s x
in (y, close f s')
-- function composition
(<.>) :: Stateful a b -> Stateful b c -> Stateful a c
f <.> g = stateful $ \x -> let (y, f') = runStateful f x
(z, g') = runStateful g y
in (z, f' <.> g')
-- lifts a stateful computation to perform on lists
maps :: Stateful a b -> [a] -> [b]
maps f [] = []
maps f (x:xs) = let (x',f') = runStateful f x
in x' : maps f' xs
So the idea is you have a function that takes and returns a state like (a -> s -> (b, s))
, but we modify it so that instead of returning an output/state tuple we return an output/X tuple, where X is the same type as our initial function. So its sort of like (a -> (b, a -> (b, ...)))
. I was initially trying to make something that resembled the State monad, but instead of returning the state returned the same function as itself, with the state partially applied (so the state and the function it's to be applied to remain coupled together).
So I thought maybe this was simply what the State monad does, but one of the use cases I want is to be able to chain multiple stateful functions together, each of which has its own state independent of the others. I haven't come up with a way to chain multiple independent functions of types (s -> a -> (b, s))
and (s' -> b -> (c, s'))
together in an elegant way, that wont result in the returned state being some ugly sequence of tuples like (s''', (s'', (s', s)))
the more chains you do, so that makes me think maybe this is more complicated than the State monad.
I also read about arrows, which are described with circuitry metaphors, as well as being used for stream processing, which fits what I'm envisioning. And I saw there's an ArrowLoop, but when I spent a while trying to figure it out it seemed like it had more to do with clever use of laziness than handling updating state. Then I also read that the IO monad uses some type system trickery to be inescapable, which kind of feels like what I've accomplished with Stateful; for one the type of Stateful doesn't encode the type of its internal state, so it would be difficult (impossible?) to infer the type of execStateful
. On the other hand, I can't come up with any bind operator that makes sense.
So I'm not really sure what I'm working with. I think it's at least a functor, since it makes sense that fmap would just be composing an arbitrary function with the end of the circuit, ensuring that the internal state management remains untouched. I almost convinced myself bind could be defined the same way, but whenever I try to make that idea concrete it falls apart. Can anyone shed some light on what I'm working with? Is this some general monad or arrow? A particular monad or arrow?
Also, are there any obvious problems or improvements that stand out to using this approach? I'd like the internal states to be able to use the ST monad, since I'm envisioning the internal states to consist of expensive in-place operations. But I don't really understand the various abstractions to know if my design is extensible.
EDIT: One thing I need to do is make sure that the original Stateful function can't be referred to, since it would refer to old states rather than the new updated ones, and I'm not sure how to go about doing that. Essentially runStateful can only be used on a Stateful datatype once, and then you need to ensure the next time you run it you use the returned Stateful a b
rather than the original one. The internal plumbing can make it pretty trivial to prevent accidentally, but I'm not sure how to make it impossible.