2

I'm testing a REST server. I hit it in the IO monad and simulate it in State Db where Db tracks the supposed state of the server. The following function is supposed to run both versions and compare the results...

check :: (Eq a, MonadState d s) => s a -> IO a -> s (IO Bool)
-- or: check :: (Eq a, MonadState d s, MonadIO i) => s a -> i a -> s (i Bool)
check _ _ = (return.return) False -- for now

but when I try it with these simplest possible functions ...

simReset :: State Db ()
realReset :: IO ()

reset :: StateT Db IO Bool
reset = check simReset realReset

I get this error:

Couldn't match expected type `Bool' with actual type `IO Bool'
Expected type: StateT Db IO Bool
  Actual type: StateT Db IO (IO Bool)
In the return type of a call of `check'
In the expression: check simReset realReset

Why? And how do I fix it?

(This topic started here: Lift to fix the *inside* of a monad transformer stack)

Community
  • 1
  • 1
Adrian May
  • 2,127
  • 15
  • 24
  • it's can't `s (IO Bool)`; `simReset` gives you `s ~ State`, but the context of reset uses `StateT`, which is different. Also you can't use state from `StateT` inside of a function defined on `State`; either change `simReset` to a generic `MonadState` or `hoist` it. – Bartek Banachewicz Dec 01 '14 at 15:17

1 Answers1

2

In your implementation, check is going to return an IO Bool regardless of what the state monad s is. So, when you pass simReset to check, which is a monadic action in the State Db monad, the return value is going to be State Db (IO Bool).

How to fix it depends on what you're trying to do. Based on your implementation of reset it seems like you're trying to interface with a transformer stack of the form StateT Db IO a. In this case, you're describing a kind of program in the context of StateT Db IO. There are two ways to go about this:

  1. You can upgrade simReset to have type MonadState Db s => s () (This shouldn't actually require any implementation change).

  2. You can define an auxiliary function that "hoists" it into the proper monad

For example:

hoistState :: Monad m => State s a -> StateT s m a
hoistState prg = StateT $ \st -> return $ runState prg st

In either case, you'll probably want to keep the action that you're performing and the result in the same monad:

check :: (Eq a, MonadIO s, MonadState d s) => s a -> IO a -> s Bool

Then, with solution one, you have

reset = check simReset realReset

And with solution two you have:

reset = check (hoistState simReset) realReset      
Mokosha
  • 2,737
  • 16
  • 33
  • Crazy deja vu. I feel so validated that these are [exactly the same two approaches I suggested](http://stackoverflow.com/a/27206159/791604) on his previous question. – Daniel Wagner Dec 01 '14 at 16:44
  • I can now make it work both ways, but in spite of you both saying the same thing I still don't understand the original problem. You say what I passed as the first parameter was getting matched with the return value. Why? I never told it to. I never even called it. What I think I did was constrain `a` to `()` with the second parameter which would constrain `s` to `State Db` and the return value to `State Db (IO Bool)` like I wanted. I just don't see what's forcing `s` to be `State Db IO`. – Adrian May Dec 02 '14 at 02:38
  • @AdrianMay I'm not sure how to explain what's wrong in your thinking, for two reasons: 1. You're not being careful about the difference between `State` and `StateT`. I'm not sure whether this is just mis-typing or a real source of confusion. 2. You say you wanted `State Db (IO Bool)`, but the code you wrote has a type signature saying you wanted `StateT Db IO Bool`. Again I'm not sure whether this is just mis-typing or a real source of confusion. If you can clear these things up, we might be able to give some more explanation, but I'm not sure. – Daniel Wagner Dec 02 '14 at 03:13
  • I thought `State X` was just `StateT X Identity`. I don't think I'm confused about the fact that `StateT X` wants a monad to operate on (which itself needs a type) whereas `State X` just needs that type. As for the return type of my function, I don't really care as long as it lets me express the equality or otherwise of two results, one of which came from some kind of stateful computation and the other of which came from an HTTP GET. – Adrian May Dec 02 '14 at 03:24
  • @AdrianMay `check :: ... -> s (IO Bool)` claims that `check` returns a monadic action producing an `IO` action (which produces a `Bool`). On the other hand, `reset :: StateT Db IO Bool` claims that `reset` returns a monadic action that directly produces a `Bool`. These two claims are not compatible, since `reset` is implemented by calling `check` -- your monadic action can't be producing something which is at the same time a `Bool` and an `IO` action. – Daniel Wagner Dec 02 '14 at 17:10