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.