状态和错误 monad 堆栈,错误时状态回滚
State and error monad stack with state rollback on error
我有一个问题,我将通过以下示例来说明:
假设我想做一些可以产生结果或错误的计算,同时携带一个状态。为此,我有以下 monad 堆栈:
import Control.Monad.Trans.State ( get, modify, State )
import Control.Monad.Trans.Except ( catchE, throwE, ExceptT )
type MyMonad a = ExceptT String (State [Int]) a
因此,状态是一个整数列表,错误是字符串,计算可以产生任何类型“a”的值。我可以做这样的事情:
putNumber :: Int -> MyMonad ()
putNumber i = lift $ modify (i:)
现在,假设我定义了一个将最后一个数字的一半添加到状态的函数:
putHalf :: MyMonad ()
putHalf = do
s <- lift get
case s of
(x:_) -> if even x then putNumber (div x 2) else throwE "Number can't be halved"
[] -> throwE "The state is empty"
使用 putHalf
将向状态添加一个数字并且 return 无效,或者产生两个错误中的任何一个。
如果发生错误,我希望能够调用替代函数。我知道我可以用 catchE 做这样的事情来实现这一点:
putWithAlternative :: MyMonad ()
putWithAlternative = putHalf `catchE` (\_ -> putNumber 12)
在这种情况下,如果 putHalf
由于任何原因失败,数字 12 将被添加到状态中。到目前为止一切都很好。但是,我可以定义一个调用 putHalf
两次的函数:
putHalfTwice :: MyMonad ()
putHalfTwice = putHalf >> putHalf
问题是,例如,如果状态仅包含数字 2,则第一次调用 putHalf
会成功并修改状态,但第二次调用会失败。我需要 putHalfTwice
进行两次调用并修改状态两次,或者根本不需要 none 并保持状态不变。我不能使用catchE
或putWithAlternative
,因为状态在第一次调用时仍然被修改。
我知道 Parsec 库通过它的 <|>
和 try
运算符来做到这一点。我怎么能自己定义这些呢?是否有任何已经定义的 monad 转换器可以实现这一点?
如果在您的问题域中,失败永远不应该修改状态,那么最直接的做法就是反转层:
type MyMonad' a = StateT [Int] (Except String) a
您的原始 monad 同构于:
s -> (Either e a, s)
所以它总是returns一个新的状态,无论是成功还是失败。这个新的 monad 同构于:
s -> Either e (a, s)
所以要么失败要么returns一个新的状态。
以下程序在不破坏状态的情况下从 putHalfTwice
恢复:
import Control.Monad.Trans
import Control.Monad.Trans.State
import Control.Monad.Trans.Except
type MyMonad' a = StateT [Int] (Except String) a
putNumber :: Int -> MyMonad' ()
putNumber i = modify (i:)
putHalf :: MyMonad' ()
putHalf = do
s <- get
case s of
(x:_) -> if even x then putNumber (div x 2) else lift $ throwE "Number can't be halved"
[] -> lift $ throwE "the state is empty"
putHalfTwice :: MyMonad' ()
putHalfTwice = putHalf >> putHalf
foo :: MyMonad' ()
foo = liftCatch catchE putHalfTwice (\_ -> putNumber 12)
main :: IO ()
main = do
print $ runExcept (runStateT foo [2])
否则,如果您希望回溯是可选的,那么您可以编写自己的 try
来捕获、恢复状态并重新抛出:
try :: MyMonad a -> MyMonad a
try act = do
s <- lift get
act `catchE` (\e -> lift (put s) >> throwE e)
然后:
import Control.Monad.Trans
import Control.Monad.Trans.State
import Control.Monad.Trans.Except
type MyMonad a = ExceptT String (State [Int]) a
putNumber :: Int -> MyMonad ()
putNumber i = lift $ modify (i:)
putHalf :: MyMonad ()
putHalf = do
s <- lift get
case s of
(x:_) -> if even x then putNumber (div x 2) else throwE "Number can't be halved"
[] -> throwE "The state is empty"
putHalfTwice :: MyMonad ()
putHalfTwice = putHalf >> putHalf
try :: MyMonad a -> MyMonad a
try act = do
s <- lift get
act `catchE` (\e -> lift (put s) >> throwE e)
foo :: MyMonad ()
foo = putHalfTwice `catchE` (\_ -> putNumber 12)
bar :: MyMonad ()
bar = try putHalfTwice `catchE` (\_ -> putNumber 12)
main :: IO ()
main = do
print $ runState (runExceptT foo) [2]
print $ runState (runExceptT bar) [2]
我有一个问题,我将通过以下示例来说明:
假设我想做一些可以产生结果或错误的计算,同时携带一个状态。为此,我有以下 monad 堆栈:
import Control.Monad.Trans.State ( get, modify, State )
import Control.Monad.Trans.Except ( catchE, throwE, ExceptT )
type MyMonad a = ExceptT String (State [Int]) a
因此,状态是一个整数列表,错误是字符串,计算可以产生任何类型“a”的值。我可以做这样的事情:
putNumber :: Int -> MyMonad ()
putNumber i = lift $ modify (i:)
现在,假设我定义了一个将最后一个数字的一半添加到状态的函数:
putHalf :: MyMonad ()
putHalf = do
s <- lift get
case s of
(x:_) -> if even x then putNumber (div x 2) else throwE "Number can't be halved"
[] -> throwE "The state is empty"
使用 putHalf
将向状态添加一个数字并且 return 无效,或者产生两个错误中的任何一个。
如果发生错误,我希望能够调用替代函数。我知道我可以用 catchE 做这样的事情来实现这一点:
putWithAlternative :: MyMonad ()
putWithAlternative = putHalf `catchE` (\_ -> putNumber 12)
在这种情况下,如果 putHalf
由于任何原因失败,数字 12 将被添加到状态中。到目前为止一切都很好。但是,我可以定义一个调用 putHalf
两次的函数:
putHalfTwice :: MyMonad ()
putHalfTwice = putHalf >> putHalf
问题是,例如,如果状态仅包含数字 2,则第一次调用 putHalf
会成功并修改状态,但第二次调用会失败。我需要 putHalfTwice
进行两次调用并修改状态两次,或者根本不需要 none 并保持状态不变。我不能使用catchE
或putWithAlternative
,因为状态在第一次调用时仍然被修改。
我知道 Parsec 库通过它的 <|>
和 try
运算符来做到这一点。我怎么能自己定义这些呢?是否有任何已经定义的 monad 转换器可以实现这一点?
如果在您的问题域中,失败永远不应该修改状态,那么最直接的做法就是反转层:
type MyMonad' a = StateT [Int] (Except String) a
您的原始 monad 同构于:
s -> (Either e a, s)
所以它总是returns一个新的状态,无论是成功还是失败。这个新的 monad 同构于:
s -> Either e (a, s)
所以要么失败要么returns一个新的状态。
以下程序在不破坏状态的情况下从 putHalfTwice
恢复:
import Control.Monad.Trans
import Control.Monad.Trans.State
import Control.Monad.Trans.Except
type MyMonad' a = StateT [Int] (Except String) a
putNumber :: Int -> MyMonad' ()
putNumber i = modify (i:)
putHalf :: MyMonad' ()
putHalf = do
s <- get
case s of
(x:_) -> if even x then putNumber (div x 2) else lift $ throwE "Number can't be halved"
[] -> lift $ throwE "the state is empty"
putHalfTwice :: MyMonad' ()
putHalfTwice = putHalf >> putHalf
foo :: MyMonad' ()
foo = liftCatch catchE putHalfTwice (\_ -> putNumber 12)
main :: IO ()
main = do
print $ runExcept (runStateT foo [2])
否则,如果您希望回溯是可选的,那么您可以编写自己的 try
来捕获、恢复状态并重新抛出:
try :: MyMonad a -> MyMonad a
try act = do
s <- lift get
act `catchE` (\e -> lift (put s) >> throwE e)
然后:
import Control.Monad.Trans
import Control.Monad.Trans.State
import Control.Monad.Trans.Except
type MyMonad a = ExceptT String (State [Int]) a
putNumber :: Int -> MyMonad ()
putNumber i = lift $ modify (i:)
putHalf :: MyMonad ()
putHalf = do
s <- lift get
case s of
(x:_) -> if even x then putNumber (div x 2) else throwE "Number can't be halved"
[] -> throwE "The state is empty"
putHalfTwice :: MyMonad ()
putHalfTwice = putHalf >> putHalf
try :: MyMonad a -> MyMonad a
try act = do
s <- lift get
act `catchE` (\e -> lift (put s) >> throwE e)
foo :: MyMonad ()
foo = putHalfTwice `catchE` (\_ -> putNumber 12)
bar :: MyMonad ()
bar = try putHalfTwice `catchE` (\_ -> putNumber 12)
main :: IO ()
main = do
print $ runState (runExceptT foo) [2]
print $ runState (runExceptT bar) [2]