I currently have two monads who share the same types, implemented similar to a State
monad:
newtype FooRead a = FooRead { runFooRead :: Context -> (a,Context) }
newtype FooWrite a = FooWrite { runFooWrite :: Context -> (a,Context) }
The difference between them is that the first only allows reading of the context (bind will not change it), while the second allows editing the context as well.
There are then functions who use the context in FooRead
to compute some value without changing the state of the context:
getVal :: FooRead a
getVal = do x <- ...
return x
Now I want to execute one of these reading functions from code in the writer monad:
writeFunc :: FooWrite ()
writeFunc = do x <- liftVal getVal
...
Where liftVal :: FooRead a -> FooWrite a
is a function that extracts the value returned by the FooRead
function and rolls it into a FooWrite
monad. This is all good.
However, I can not figure out a way to roll the execution of getVal
in the above into the context from my FooWrite
monad. With the above implementation, getVal
will be run in an empty instance of the monad.
I can figure out how to construct a FooRead
instance using a FooWrite
context as
lower :: FooWrite a -> FooRead a
Essentially I want to demote my writer to a reader, execute code within the reader, the re-promote it to a writer.
But not how to actually execute code within this monad?
Here's how I'd implement this. First, if you want Writer
to be strictly more powerful than Reader
than we'd want a function
liftReader :: FooReader a -> FooWriter a
liftReader (FooReader a) = FooWriter a
This works because they're structurally equivalent and their monad instances should be isomorphic.
Then we can just
t :: FooWriter Int
t = liftReader getVal
If you want to be able to go the other way, it's pretty easy
liftWriter :: FooWriter a -> FooReader a
liftWriter (FooWriter a) = FooReader a
Now the fact that we can lift these types into each other makes you think that they're somehow equivalent.. And in fact they are, you basically have
import Control.Monad.State
newtype FooReader s a = FooReader (State s a)
newtype FooWriter s a = FooWriter (State s a)
And State
provides get
and put
which are analogous to your getVal
and writeFunc