你接近了答案。让我们跟随类型上的类型洞(
_
):
impuree :: String -> PlayMIO String
impuree s = do
a <- _ . runIdentity . runExceptT $ puree s
return $ "aa" ++ a
这告诉我们需要一个类型:
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
现在,我们有了一种可以将 m (Either e b)
转换成 ExceptT e m b
的方法:
ExceptT :: m (Either e b) -> ExceptT e m b
应用这个方法,我们得到了正确答案:
impuree :: String -> PlayMIO String
impuree s = do
a <- ExceptT . return . runIdentity . runExceptT $ puree s
return $ "aa" ++ a
如果我们查看文档,可以看到模式
ExceptT . f . runExceptT
是通过函数进行抽象化的。
mapExceptT :: (m (Either e a) -> n (Either e' b)) -> ExceptT e m a -> ExceptT e' n b
在我们的情况下,
m
是
Identity
,而
n
是
IO
。使用这个,我们得到:
impuree :: String -> PlayMIO String
impuree s = do
a <- mapExceptT (return . runIdentity) $ puree s
return $ "aa" ++ a
抽象出模式
这可能在这里有些过于复杂,但值得注意的是,有一个名为mmorph
的包,使得使用单子变形(从一个单子到另一个单子的转换)更加方便。该包具有函数generalize :: Monad m => Identity a -> m a
,我们可以用它来:
impuree :: String -> PlayMIO String
impuree s = do
a <- mapExceptT generalize $ puree s
return $ "aa" ++ a
更进一步的概括
既然我们正在谈论 mmorph
,我们不妨使用更一般化的形式:
impuree :: String -> PlayMIO String
impuree s = do
a <- hoist generalize $ puree s
return $ "aa" ++ a
hoist
通用化了mapExceptT
的功能,适用于所有类似于单子变换器的东西,在其中你可以将一个单子态射应用到底层单子上:
hoist :: (MFunctor t, Monad m) => (forall a. m a -> n a) -> t m b -> t n b
在这里第一个正确答案之后的所有内容都是额外的,并且不需要理解它以理解和使用解决方案。但是在某些情况下可能会有用,这就是为什么我包含它的原因。识别单子态射的一般模式可以节省时间,但是您始终可以更明确地执行操作,而不需要额外的抽象层次。
EitherT
是错误地出现了。 - dfeuerEitherT
- Cirdec