Reading "Write Yourself a Scheme in 48 hours" and am confused on this page https://en.wikibooks.org/wiki/Write_Yourself_a_Scheme_in_48_Hours/Adding_Variables_and_Assignment by:
getVar :: Env -> String -> IOThrowsError LispVal
getVar envRef var = do env <- liftIO $ readIORef envRef
maybe (throwError $ UnboundVar "Getting an unbound variable" var)
(liftIO . readIORef)
(lookup var env)
I'm not entirely clear how the type is resolved. Here's my reasoning:
envRef
is of type IORef [(String, IORef LispVal)]
, so readIORef envRef
is of type IO [(String, IORef LispVal)]
.
Now LiftIO
is defined at http://hackage.haskell.org/package/transformers-0.4.1.0/docs/Control-Monad-IO-Class.html as being of type liftIO :: IO a -> m a
and is implemented at https://hackage.haskell.org/package/mtl-1.1.0.2/docs/src/Control-Monad-Error.html#ErrorT. So liftIO $ readIORef envRef
returns m [(String, IORef LispVal)]
for some monad m (which doesn't matter because we just use <-
on it straight away so ignore the monad wrapper) [1].
This means env is [(String, IORef LispVal)]
, so lookup var env
is Maybe IORefLispVal
. For the Just branch of the maybe, liftIO . readIORef
will be m LispVal
, again for some monad m. Given the whole function returns an IOThrowsError LispVal
(which is just an ErrorT LispError IO LispVal
i.e. IO Either LispError LispVal
), so m must be an IOThrowsError
[2].
Now if there are different monad transformers in place, I guess that there could be more than one liftIO with a different type signature in scope. Is this actually the case and if so, to what extent is the chain of reasoning above representative of how Haskell actually determines the types? I'm not happy with the reasoning at [1] or [2], so there's a secondary question of how the monad for liftIO is determined. And finally for a do statement how does do know what monad it's in? Is this determined by the first monad after the <-
? Or some other way?