Haskell 状态和错误monad堆栈,错误时状态回滚
我有一个问题,我将通过以下示例加以说明: 假设我想做一些计算,在携带状态的同时,可以产生结果或错误。为此,我有以下monad堆栈:Haskell 状态和错误monad堆栈,错误时状态回滚,haskell,state-monad,Haskell,State Monad,我有一个问题,我将通过以下示例加以说明: 假设我想做一些计算,在携带状态的同时,可以产生结果或错误。为此,我有以下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 因此,状态是一个整数列表,错误是
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"
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])
在这种情况下,如果由于任何原因,putshalf
失败,数字12将添加到状态。到目前为止,一切都很好。但是,我可以定义一个调用putshalf
两次的函数:
putHalfTwice :: MyMonad ()
putHalfTwice = putHalf >> putHalf
问题是,例如,如果状态仅包含数字2,则对putshalf
的第一次调用将成功并修改状态,但第二次调用将失败。我需要两次puthalftweep
来执行两次调用并修改状态两次,或者根本不修改状态并保持状态不变。我不能使用catch
或putWithAlternative
,因为状态在第一次调用中仍然被修改
我知道Parsec库是通过它的
和try
操作符来实现的。我怎么能自己定义这些呢?是否有任何已经定义的monad transformer可以实现这一点?如果在您的问题域中,故障永远不会改变状态,最简单的方法是反转层:
type MyMonad' a = StateT [Int] (Except String) a
您的原始单子同构于:
s -> (Either e a, s)
s -> Either e (a, s)
因此,无论成功与否,它总是返回一个新状态。此新单子同构于:
s -> (Either e a, s)
s -> Either e (a, s)
因此,它要么失败,要么返回新状态
以下程序从PutHalf中恢复两次,而不会损坏状态:
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"
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])
然后:
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]
import Control.Monad.Trans
进口管制.单子.跨州
进口管制.单子交易.例外
键入MyMonad a=ExceptT字符串(State[Int])a
putNumber::Int->MyMonad()
putNumber i=提升$modify(i:)
putHalf::MyMonad()
putHalf=do
如果是偶数x,则输入数字(div x 2),否则通过“数字不能减半”
[]->通过“状态为空”
PutHalf两次::MyMonad()
putHalf两次=putHalf>>putHalf
try::MyMonad a->MyMonad a
试着去做
s举(放s)>>throwE)
foo::MyMonad()
foo=putHalf两次'catch'(\ \ \ \->putNumber 12)
bar::MyMonad()
bar=尝试两次PutHalf'catch`(\\\->putNumber 12)
main::IO()
main=do
打印$runState(runexcepttfoo)[2]
打印$runState(runExceptT条)[2]
这很有效!我不确定这样实现try
是否可以,因为我将状态的副本保存在内存中,但我认为没有其他方法。谢谢你,我有一个问题,为什么你要使用liftCatch
而不仅仅是lift
?lift
就足以将表达式的类型action1`catchE`action2
从除了String
提升到MyMonad
,但是参数action1
和action2
不会被取消——它们在中仍然有类型,除了字符串monad而不是MyMonad
。这就是为什么各个转换器提供一个liftCatch
函数,专门用于通过转换器提升liftCatch
的参数和返回值。