I have the following little working program:
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Control.Monad.State.Strict
import Control.Monad.Except
import qualified Data.Text as T
import qualified Data.Text.Read as T
import qualified Data.Text.IO as T
main :: IO ()
main = do
_ <- execStateT loop 0
return ()
loop :: StateT Int IO ()
loop = do
liftIO $ putStrLn "Enter a Number"
line <- liftIO $ T.getLine
let ts = T.words line
checkFoo ts
loop
checkFoo :: (MonadState Int m, MonadIO m) => [T.Text] -> m()
checkFoo strs = liftIO (runExceptT
(check1 strs >>= checkNum >>= doFoo)) >>= result
where
doFoo n = liftIO $ putStrLn $ "Your number: " <> show n
check1 :: [a] -> ExceptT T.Text IO a
check1 ts =
if length ts == 1
then return $ head ts
else throwError "1 number please"
checkNum :: T.Text -> ExceptT T.Text IO Int
checkNum t = case T.decimal t of
Left _ -> throwError "input isn't a number"
Right (d, _) -> return $ d
result :: (MonadState Int m, MonadIO m) => Either T.Text () -> m ()
result (Left e) = liftIO $ T.putStrLn e
result (Right _) = return ()
Now I would like to access the value of the State Monad in the subfunction doFoo of my function checkFoo. eg:
doFoo n =
old <- lift get
let s = old + n
liftIO $ putStrLn $ "The sum of your numbers: " <> show s
lift $ put s
pure ()
I get the following error:
Main.hs:26:35: error:
• Could not deduce (MonadState a0 IO) arising from a use of ‘doFoo’
from the context: (MonadState Int m, MonadIO m)
bound by the type signature for:
checkFoo :: forall (m :: * -> *).
(MonadState Int m, MonadIO m) =>
[T.Text] -> m ()
at Main.hs:24:1-60
The type variable ‘a0’ is ambiguous
• In the second argument of ‘(>>=)’, namely ‘doFoo’
In the first argument of ‘runExceptT’, namely
‘(check1 strs >>= checkNum >>= doFoo)’
In the first argument of ‘liftIO’, namely
‘(runExceptT (check1 strs >>= checkNum >>= doFoo))’
|
26 | (check1 strs >>= checkNum >>= doFoo)) >>= result
Why is this not working? What are the necessary changes to make this working?
The problem is that when you write the expression:
this requires that each of these operations is an action is the same monad. The monad for the first two is:
which implies a type signature for
doFoo:but then you try to lift
putandgetoperations in your reviseddoFoodefinition. The error message is telling you that these operations aren't supported by the stateless monadExceptT T.Text IO.The least disruptive fix is probably to modify the type signatures for
check1andcheckNumto generalize them over anyMonadIO m:Then,
checkFoocan be written as follows, without theliftIObeforerunExcept. I've also removed theliftbeforegetandput. They aren't necessary, asgetandputautomatically lift themselves to the closest containingStateTtransformer.This version runs the pipeline
check1 strs >>= checkNum >>= doFooin the monadExceptT T.Text mwheremis the same monad(MonadState Int m, MonadIO m) => mthat appears incheckFoo's type signature. The result is passed toresult :: (MonadIO m) => Either T.Text () -> m ()again for that same monadm. In your code,checkFoois called atm ~ StateT Int IO, which satisfies the constraintsMonadState Int mandMonadIO m, so all is well with the type checker.The full revised example: