将StateT移入和移出IO

时间:2015-08-19 22:42:22

标签: haskell monads

我确定我必须遗漏一些东西。

我是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

2 个答案:

答案 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

在我们的案例中,mIdentitynIO。使用这个,我们得到:

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

hoistmapExceptT概括为任何monad变换器类似的东西,你可以将monad态射应用于基础monad:

hoist :: (MFunctor t, Monad m) => (forall a. m a -> n a) -> t m b -> t n b

这里第一个正确答案之后的所有内容都只是奖金,为了理解和使用解决方案,没有必要理解它。它可以在某些方面派上用场,这就是我加入它的原因。认识到monad态射的一般模式可以节省时间,但是如果没有额外的抽象级别,你总是可以更明确地做事。

答案 1 :(得分:2)

另一种方法是简单地使您的pureeimpuree操作类型类具有多态性。这是通常的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"

在此示例中,您的代码特别无趣,因为您没有使用IOExceptT的任何特殊权限。如果您有以下情况,这就是它的样子:

-- 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"