将 StateT 移入和移出 IO
Moving StateT into and out of IO
我确定我一定遗漏了什么。
我是 Haskell 的新手,曲线非常陡峭。在我的玩具项目中,我已经到了我真的想使用 State monad 来避免到处传递一千个参数的地步。我无法理解如何将 State monad 从 IO 传递到纯代码中。概念上是这样的(除了使用 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
你很接近。让我们按照带有类型孔 (_
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
which makes it nicer to work with monad morphisms (transformations from one monad to another). This package has a function 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 态射的一般模式可以节省时间,但您始终可以更明确地做事而无需额外的抽象级别。
另一种方法是简单地使您的 puree
和 impuree
操作类型为 class 多态。这是通常的 mtl
方式:需要一些 classes,然后在顶层某处选择一个实例化所有适当 classes 的具体 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"
我确定我一定遗漏了什么。
我是 Haskell 的新手,曲线非常陡峭。在我的玩具项目中,我已经到了我真的想使用 State monad 来避免到处传递一千个参数的地步。我无法理解如何将 State monad 从 IO 传递到纯代码中。概念上是这样的(除了使用 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
你很接近。让我们按照带有类型孔 (_
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
which makes it nicer to work with monad morphisms (transformations from one monad to another). This package has a function 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 态射的一般模式可以节省时间,但您始终可以更明确地做事而无需额外的抽象级别。
另一种方法是简单地使您的 puree
和 impuree
操作类型为 class 多态。这是通常的 mtl
方式:需要一些 classes,然后在顶层某处选择一个实例化所有适当 classes 的具体 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"