我确定我必须遗漏一些东西。
我是Haskell的新手,曲线非常陡峭。我正在达到我的玩具项目的重点,我真的想要使用State monad来避免在任何地方传递一千个论点。我无法理解如何将IO中的State monad传递给纯代码。概念上像这样的东西(除了StateT而不是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"
除了这不编译,给我这样的东西:
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’
我现在明白为什么这不会编译,为什么这些类型就是它们,但对于我的生活,我无法理解如何做到这一点。这感觉不应该很难,但我对Haskell的直觉远非准确。
感谢您的帮助!
-g
答案 0 :(得分:5)
你很亲密。让我们按照类型为洞的类型(_
s):
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
的软件包可以更好地处理monad态射(从一个monad转换到另一个monad)。这个包有一个我们可以使用的函数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
概括为任何monad变换器类似的东西,你可以将monad态射应用于基础monad:
hoist :: (MFunctor t, Monad m) => (forall a. m a -> n a) -> t m b -> t n b
这里第一个正确答案之后的所有内容都只是奖金,为了理解和使用解决方案,没有必要理解它。它可以在某些方面派上用场,这就是我加入它的原因。认识到monad态射的一般模式可以节省时间,但是如果没有额外的抽象级别,你总是可以更明确地做事。
答案 1 :(得分:2)
另一种方法是简单地使您的puree
和impuree
操作类型类具有多态性。这是通常的mtl
方式:需要一些类,然后在顶层的某个地方选择一个实例化所有适当类的具体monad。因此:
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"
在此示例中,您的代码特别无趣,因为您没有使用IO
或ExceptT
的任何特殊权限。如果您有以下情况,这就是它的样子:
-- 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"