使用不同类型的短路进行状态计算(可能,或者) [英] Stateful computation with different types of short-circuit (Maybe, Either)
问题描述
我试图找到将以下有状态命令式代码片段转换为纯函数式表示的最优雅方式(最好在Haskell中使用其Monad实现提供的抽象方式)。不过,我还不擅长使用变形金刚等组合不同的monads。在我看来,分析其他人承担这样的任务有助于在学习如何自己做到最好的时候。命令式代码:
while(true){
while(x = get()){//想想作为返回也许某事
put1(x)//可以退出并呈现一些失败表示
}
put2()//可以退出并呈现一些成功表示
}
当 get
返回 Nothing 当
我们希望将 get
返回 put2
>只要x x
传递给 put1
并且仅限于短路如果 put1
失败或以其他方式循环。基本上, put1
和 put2
可能会终止整个事情,或者移动到下面的语句中,以某种方式更改基础状态。 get
可以成功并调用 put1
并循环或失败并继续执行 put2
我的想法如下:
永远$ do
forever(get>> = put1)
put2
为什么我在寻找类似的东西,是因为(get>> = put1)
只要 get
没有任何东西可以返回,或者 put1
终止。同样, put2
终止外部循环。但我不确定如何将 State
与必要的 Maybe
和/或混合
我认为使用变形金刚来结合 State
和其他monads是必要的,因此代码很可能不会是那种成功。但我想它可能不会更糟。
欢迎任何有关如何优雅地完成翻译的建议。这与有状态循环与不同类型的中断有所不同使用 if
,时
, while
的显式控制流和而是试图鼓励使用 Maybe
,或者
或者其他方便的>> =
语义。此外,如何将代码转化为功能性代码总是一种直接的方式,但它很难被认为是优雅的。
p>您正在寻找 EitherT
或 ExceptT
。它增加了两种方法返回到变压器堆栈。计算可以返回一个
或 throwError e
。错误和回报有两个区别。在 Left
上存在错误,并在 Right
上返回。当>> =
出现错误时,它会短路。 > newtype EitherT ema = EitherT {runEitherT :: m(ea)}
return :: a - >或者T $ a
return a = EitherT $ return(Right a)
throwError :: e - > Either
throwError e = EitherT $ return(Left a)
我们也将使用 left = throwError
和 right = return
。
Left
中的错误不会继续,我们将使用它们来表示从循环中退出。我们将使用类型 EitherT rm()
来表示一个循环,该循环或者以中断结果 Left r
或者继续使用 Right()
。这几乎完全是永远
,除了我们解开 EitherT
并摆脱 Left
围绕返回的值。
import Control.Monad
import Control.Monad.Trans。或者
untilLeft :: Monad m =>或者T r m() - > m r
untilLeft = liftM(id id)。 runEitherT。永远
我们将回到如何在充实您的示例之后使用这些循环。
既然你想看到几乎所有的逻辑都消失了,我们也会使用 EitherT
。获取数据的计算是 Done
或返回数据。
导入Control.Monad.Trans.Class
导入Control.Monad.Trans.State
数据完成=完成导出显示
- 获取一段时间的数字。
get1 :: EitherT Done(State Int)Int
get1 = do
x< - 电梯获得
电梯。放$ x + 1
如果x`mod` 3 == 0
then left Done
else right x
放入数据的第一个计算是
数据失败=失败导出显示
put1 :: Int - > EitherT Failure(State Int)()
put1 x = if x`mod` 16 == 0
then left失败
else right()
放入数据的第二个计算是
data成功=导出成功显示
put2 :: EitherT成功(State Int)()
put2 = do
x< - lift如果x`mod`获得
25 == 0
则保留成功
else right()
对于您的示例,我们需要将两个或更多计算结合起来,这些计算都以不同的方式异常停止。我们将用两个嵌套 EitherT
s来表示它。
EitherT o EitherT im)r
外部 EitherT
是我们目前正在经营的一个。我们可以通过添加一个额外的将
层。 EitherT oma
转换为 EitherT o(EitherT im)a
>每个 m
的EitherT
over ::(MonadTrans t,Monad m)=>或者T e m a - >或者T e(tm)a
over = mapEitherT lift
内部 EitherT
图层将被视为与变换器堆栈中的其他任何基础monad一样。我们可以提升
an EitherT ima
到 EitherT o(EitherT im)a
现在我们可以构建一个成功或失败的整体计算。将打破当前循环的计算操作 over
。计算会破坏外部循环: lift
ed。
例如: :EitherT失败(State Int)成功
示例=
untilLeft $做
提升。 untilLeft $ over get1>> = lift。 put1
over put2
整体失败
是 lift
两次进入最内层循环。这个例子非常有趣,可以看到几个不同的结果。
main = print。 map(runState $ runEitherT example)$ [1..30]
†如果 EitherT
有一个 MFunctor
实例, over
只是提升提升
,这是一种经常使用的模式,它应该有自己深思熟虑的名字。顺便提一句,我使用 EitherT
over ExceptT
主要是因为它的名称较少。无论哪个人首先提供一个 MFunctor
实例,对于我来说,最终都会以单变量形式获胜。
I am trying to find the most elegant way of converting the following stateful imperative piece of code to pure functional representation (preferably in Haskell to use abstraction that its Monad implementation offers). However I am not yet good at combining different monads using transformers and the like. It seems to me, that analyzing other's takes on such tasks helps the best when learning how to do it myself. The imperative code:
while (true) {
while (x = get()) { // Think of this as returning Maybe something
put1(x) // may exit and present some failure representation
}
put2() // may exit and present some success representation
}
When get
returns Nothing
we need the execution to continue with put2
, when get
returns Just x
we want the x
to get passed to put1
and short-circuit only if put1
fails or loop otherwise. Basically put1
and put2
may terminate the whole thing or move to the following statement changing the underlying state somehow. get
can either succeed and invoke put1
and loop or fail and continue to put2
.
My idea was something along:
forever $ do
forever (get >>= put1)
put2
And why I was looking for something like that is because (get >>= put1)
could simply short-circuit whenever get
has nothing to return or put1
terminates. Similarly put2
terminates the outer loop. However I am not sure how to mix the State
with the necessary Maybe
and/or Either
to achieve this.
I think using transformers to combine State
and the other monads is necessary and thus the code will most probably not be that succint. But I guess it as well might not be much worse.
Any suggestion how to achieve the translation elegantly is welcome. This differs from "Stateful loop with different types of breaks" in avoiding explicit control-flow using if
, when
, while
and rather tries to encourage use of Maybe
, Either
, or some other handy >>=
semantics. Also there is always a straight-forward way how to translate the code into a functional one, however it can hardly be considered elegant.
You are looking for EitherT
or ExceptT
. It adds two ways to return to a transformer stack. The computation can either return a
or throwError e
. There are two differences between errors and returns. Errors are held on the Left
and returns on the Right
. When you >>=
onto an error it short circuits.
newtype EitherT e m a = EitherT { runEitherT :: m (Either e a) }
return :: a -> EitherT e m a
return a = EitherT $ return (Right a)
throwError :: e -> EitherT e m a
throwError e = EitherT $ return (Left a)
We will also use the names left = throwError
and right = return
.
Errors on the Left
don't continue, we will use them to represent exiting from a loop. We will use the type EitherT r m ()
to represent a loop that either stops with a breaking result Left r
or continues with a Right ()
. This is almost exactly forever
, except we unwrap the EitherT
and get rid of the Left
around the returned value.
import Control.Monad
import Control.Monad.Trans.Either
untilLeft :: Monad m => EitherT r m () -> m r
untilLeft = liftM (either id id) . runEitherT . forever
We'll come back to how to use these loops after fleshing out your example.
Since you want to see almost all of the logic disappear, we'll use EitherT
for everything else too. The computation that gets data is either Done
or returns the data.
import Control.Monad.Trans.Class
import Control.Monad.Trans.State
data Done = Done deriving Show
-- Gets numbers for a while.
get1 :: EitherT Done (State Int) Int
get1 = do
x <- lift get
lift . put $ x + 1
if x `mod` 3 == 0
then left Done
else right x
The first computation that puts data is either a Failure
or returns.
data Failure = Failure deriving Show
put1 :: Int -> EitherT Failure (State Int) ()
put1 x = if x `mod` 16 == 0
then left Failure
else right ()
The second computation that puts data is either a Success
or returns.
data Success = Success deriving Show
put2 :: EitherT Success (State Int) ()
put2 = do
x <- lift get
if x `mod` 25 == 0
then left Success
else right ()
For your example, we will need to combine two or more computations that both stop exceptionally in different ways. We will represent this with two nested EitherT
s.
EitherT o (EitherT i m) r
The outer EitherT
is the one we are currently operating over. We can convert an EitherT o m a
to an EitherT o (EitherT i m) a
by adding an extra EitherT
layer around every m
†.
over :: (MonadTrans t, Monad m) => EitherT e m a -> EitherT e (t m) a
over = mapEitherT lift
The inner EitherT
layer will be treated just like any other underlying monad in the transformer stack. We can lift
an EitherT i m a
to an EitherT o (EitherT i m) a
We can now build an overall computation that either succeeds or fails. Computations that would break the current loop are operated over
. Computations that would break an outer loop are lift
ed.
example :: EitherT Failure (State Int) Success
example =
untilLeft $ do
lift . untilLeft $ over get1 >>= lift . put1
over put2
Overall Failure
is lift
ed twice into the innermost loop. This example is sufficiently interesting to see a few different results.
main = print . map (runState $ runEitherT example) $ [1..30]
†If EitherT
had an MFunctor
instance, over
would just be hoist lift
, which is a pattern that is used so often it deserves its own well thought out name. Incidentally, I use EitherT
over ExceptT
primarily because it has a less loaded name. Whichever one provides an MFunctor
instance first will, for me, finally win out as the either monad transformer.
这篇关于使用不同类型的短路进行状态计算(可能,或者)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!