Search code examples
haskellmonadsstate-monadlifting

Execute monadic code from newly created monad


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 getValin 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?


Solution

  • 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