Moving StateT to and from IO

I'm sure something is missing for me.

I am new to Haskell and the curve is very steep. I guess in my toy project where I really want to use the state monad to avoid passing thousands of arguments around the world. I am having trouble understanding how to transfer this state monad from IO to clean code. Something conceptually similar (except with StateT instead of ExceptT):

import Control.Monad.Except import Control.Monad.Identity type PlayM = Except String type PlayMIO = ExceptT String IO puree :: String -> PlayM String puree = return . ("bb"++) impuree :: String -> PlayMIO String impuree s = do a <- return $ runIdentity $ runExceptT $ puree s return $ "aa" ++ a main = do runExceptT $ impuree "foo" putStrLn "hi" 

other than that it doesn't compile, giving me something like this:

 play.hs:15:20: Couldn't match expected type '[Char]' with actual type 'Either String String' In the second argument of '(++)', namely 'a' In the second argument of '($)', namely '"aa" ++ a' 

Now I understand why this does not compile and why types are what they are, but for the life of me I cannot figure out how to do this. It seems like it shouldn't be difficult, but my intuition at Haskell is far from accurate.

Thanks for your help!

-g

+4
source share
2 answers

An alternative approach is to simply make your puree and impuree polymorphic type classes. This is the usual mtl method: it requires some classes, then somewhere at the top level it selects a specific monad that creates all the corresponding classes. Thus:

 import Control.Monad.Except import Control.Monad.Identity type PlayM = Except String type PlayMIO = ExceptT String IO puree :: Monad m => String -> m String puree = return . ("bb"++) impuree :: Monad m => String -> m String impuree s = do a <- puree s return $ "aa" ++ a main = do runExceptT $ impuree "foo" putStrLn "hi" 

In this example, your code is especially uninteresting because you did not use any special IO or ExceptT . Here's what it might look like if you had:

 -- in particular, puree :: String -> PlayM String puree :: MonadError String m => String -> m String puree "heck" = throwError "Watch your language!" puree s = return ("bb" ++ s) -- in particular, impuree :: String -> PlayMIO String impuree :: (MonadError String m, MonadIO m) => String -> m String impuree s = do s' <- puree s liftIO . putStrLn $ "hah! what kind of input is " ++ s ++ "?!" return ("aa" ++ s) main = do runExceptT (impuree "foo") putStrLn "hi" 
+2
source

You're close Let types with holes of type ( _ s) follow:

 impuree :: String -> PlayMIO String impuree s = do a <- _ . runIdentity . runExceptT $ puree s return $ "aa" ++ a 

This tells us that we need a type:

 Test.hs:15:8: Found hole '_' with type: m0 (Either String String) -> ExceptT String IO [Char] Where: 'm0' is an ambiguous type variable Relevant bindings include s :: String (bound at Test.hs:13:9) impuree :: String -> PlayMIO String (bound at Test.hs:13:1) In the first argument of '(.)', namely '_' In the expression: _ . return . runIdentity . runExceptT In a stmt of a 'do' block: a <- _ . return . runIdentity . runExceptT $ puree s 

Now we have something that can turn m (Either eb) into ExceptT emb :

 ExceptT :: m (Either eb) -> ExceptT emb 

Applying this, we get the correct answer:

 impuree :: String -> PlayMIO String impuree s = do a <- ExceptT . return . runIdentity . runExceptT $ puree s return $ "aa" ++ a 

If we look at the documents, we will see that the template is ExceptT . f . runExceptT ExceptT . f . runExceptT ExceptT . f . runExceptT abstracts with function

 mapExceptT :: (m (Either ea) -> n (Either e' b)) -> ExceptT ema -> ExceptT e' nb 

In our case, m is Identity , and n is IO . Using this, we get:

 impuree :: String -> PlayMIO String impuree s = do a <- mapExceptT (return . runIdentity) $ puree s return $ "aa" ++ a 

Template Testing

This is likely to be redundant here, but it is worth noting that there is a package called mmorph that facilitates working with the monad (conversion from one monad to another). This package has the function generalize :: Monad m => Identity a -> ma , which we can use:

 impuree :: String -> PlayMIO String impuree s = do a <- mapExceptT generalize $ puree s return $ "aa" ++ a 

Further generalization

While we are talking about mmorph , we can use a more general view:

 impuree :: String -> PlayMIO String impuree s = do a <- hoist generalize $ puree s return $ "aa" ++ a 

hoist generalizes mapExceptT to any monad transformer thing, where you can apply the monad morphology to the main monad:

 hoist :: (MFunctor t, Monad m) => (forall a. ma -> na) -> tmb -> tnb 

Everything after the first correct answer is simply bonus material, and there is no need to understand it in order to understand and use the solution. This might come in handy at some point, which is why I turned it on. Recognizing the general picture of monad morphisms saves time, but you can always do something more explicitly without this additional level of abstraction.

+5
source

All Articles