StateT s(ExceptT em)和ExceptT e(StateT sm)之间有什么区别?

pha*_*dej 12 haskell monad-transformers

Monad变形金刚很棘手,我不确定(=没有良好的直觉)哪一个应该是最重要的.

Jon*_*ast 15

StateT s (ExceptT e m)
Run Code Online (Sandbox Code Playgroud)

这说:

  • 从...开始 m
  • 添加例外
  • 添加状态

现在,"添加异常"意味着您的操作可以通过两种方式终止:具有正常返回值或具有异常.

"添加状态"意味着额外的状态输出包含在正常返回值中.

因此StateT s (ExceptT e m),如果没有异常,您只会获得结果状态.

另一方面,

ExceptT e (StateT s m)
Run Code Online (Sandbox Code Playgroud)

说:

  • 从...开始 m
  • 添加状态
  • 添加例外

"添加状态"意味着额外的状态输出包含在返回值中m.

但现在,让您住得异常被添加作为替代返回值里面StateT单子.因此,您总是获得状态输出,然后您可能会得到正常的返回值,或者您可能会得到一个例外.

  • 这可能会给出更清晰的图像,因此:`StateT s(ExceptT em)a`具有"扩展"形式:`s - > m(E(s,a))````ExceptT e(StateT sm) `"扩展"为:`s - > m(s,Either)`.前者允许您在`catchError`中发生错误时回滚对状态的更改,而后者提交错误状态使其更难回滚.在某些语言中,您实际上需要后者.推荐阅读:http://www.cse.chalmers.se/edu/course/TDA342_Advanced_Functional_Programming/lecture8.html (5认同)

pha*_*dej 8

我自己回答这个问题,但欢迎其他答案!

考虑这个例子:

#!/usr/bin/env stack
-- stack runghc --package mtl 

{-# LANGUAGE FlexibleContexts #-}
module Main (main) where

import Control.Applicative
import Control.Monad.State
import Control.Monad.Error
import Control.Monad.Trans.Except
import Data.Functor.Identity

test1 :: (MonadState Int m, MonadError String m) => m Bool
test1 = do
  put 1
  throwError "foobar"
  put 2
  return False

test2 :: (Alternative m, MonadState Int m, MonadError String m) => m Bool
test2 = do
  put 4
  test1 <|> return True

runStateExceptT :: Monad m => s -> ExceptT e (StateT s m) a -> m (Either e a, s)
runStateExceptT s = flip runStateT s . runExceptT

runExceptStateT :: Monad m => s -> StateT s (ExceptT e m) a -> m (Either e (a, s))
runExceptStateT s = runExceptT . flip runStateT s

main :: IO ()
main = do
  print $ runIdentity . runStateExceptT 3 $ test1
  print $ runIdentity . runExceptStateT 3 $ test1
  print $ runIdentity . runStateExceptT 3 $ test2
  print $ runIdentity . runExceptStateT 3 $ test2
Run Code Online (Sandbox Code Playgroud)

它将打印:

(Left "foobar",1)
Left "foobar"
(Right True,1)
Right (True,4)
Run Code Online (Sandbox Code Playgroud)

在ExceptT外面,你仍然会得到"抛出错误"的那一刻的状态.这可能就是你想要的.

请记住,这个组合类似于命令式编程了很多.人们应该考虑例外安全实践,即必须小心何时throwError!