使用不同类型的短路进行状态计算(可能,或者) [英] 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

放入数据的第一个计算是 Failure 或返回。

 数据失败=失败导出显示

put1 :: Int - > EitherT Failure(State Int)()
put1 x = if x`mod` 16 == 0
then left失败
else right()

放入数据的第二个计算是 Success 或返回。

  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 EitherTs.

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 lifted.

example :: EitherT Failure (State Int) Success
example =
    untilLeft $ do
        lift . untilLeft $ over get1 >>= lift . put1
        over put2

Overall Failure is lifted 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆