gpt4 book ai didi

scala - 条件状态 monad 表达式

转载 作者:行者123 更新时间:2023-12-04 07:15:22 25 4
gpt4 key购买 nike

我正在使用来自 Scala Cats 的 State monad库以功能方式组合状态转换的命令序列。

我的实际用例相当复杂,所以为了简化问题,请考虑以下最小问题:有一个 Counter保持可增加或减少的计数值的状态;但是,如果计数变为负数或溢出,则会出现错误。如果遇到错误,我需要保留错误发生时的状态并有效停止处理后续状态转换。

我使用每个状态转换的返回值来报告任何错误,使用类型 Try[Unit] .成功完成的操作然后返回新状态加上值 Success(()) ,而失败则返回现有状态以及包装在 Failure 中的异常.

注意:显然,我可以在遇到错误时抛出异常。但是,这会违反引用透明性,并且还需要我做一些额外的工作来将计数器状态存储在抛出的异常中。我也使用 Try[Counter] 打折作为状态类型(而不仅仅是 Counter ),因为我无法使用它来跟踪失败和失败状态。我还没有探索过的一种选择是使用 (Counter, Try[Unit])元组作为状态,因为这看起来太麻烦了,但我愿意接受建议。

import cats.data.State
import scala.util.{Failure, Success, Try}

// State being maintained: an immutable counter.
final case class Counter(count: Int)

// Type for state transition operations.
type Transition[M] = State[Counter, Try[M]]

// Operation to increment a counter.
val increment: Transition[Unit] = State {c =>

// If the count is at its maximum, incrementing it must fail.
if(c.count == Int.MaxValue) {
(c, Failure(new ArithmeticException("Attempt to overflow counter failed")))
}

// Otherwise, increment the count and indicate success.
else (c.copy(count = c.count + 1), Success(()))
}

// Operation to decrement a counter.
val decrement: Transition[Unit] = State {c =>

// If the count is zero, decrementing it must fail.
if(c.count == 0) {
(c, Failure(new ArithmeticException("Attempt to make count negative failed")))
}

// Otherwise, decrement the count and indicate success.
else (c.copy(count = c.count - 1), Success(()))
}

但是,我正在努力确定将转换串在一起的最佳方法,同时以所需的方式处理任何故障。 (如果您愿意,对我的问题的更一般性陈述是我需要根据前一个转换的返回值有条件地执行后续转换。)

例如,以下一组转换可能会在第一步、第三步或第四步失败(但我们假设它也可能在第二步失败),具体取决于计数器的起始状态,但它仍会尝试无条件执行下一步:

val counterManip: Transition[Unit] = for {
_ <- decrement
_ <- increment
_ <- increment
r <- increment
} yield r

如果我使用初始计数器值 0 运行此代码,显然我将得到新的计数器值 3 和 Success(()) ,因为这是最后一步的结果:

scala> counterManip.run(Counter(0)).value
res0: (Counter, scala.util.Try[Unit]) = (Counter(3),Success(()))

但我想要的是获得初始计数器状态(使 decrement 操作失败的状态)和一个 ArithmeticException包裹在 Failure ,因为第一步失败了。

到目前为止,我能想到的唯一解决方案是极其复杂、重复且容易出错:

val counterManip: Transition[Unit] = State {s0 =>
val r1 = decrement.run(s0).value
if(r1._2.isFailure) r1
else {
val r2 = increment.run(r1._1).value
if(r2._2.isFailure) r2
else {
val r3 = increment.run(r2._1).value
if(r3._2.isFailure) r3
else increment.run(r3._1).value
}
}
}

这给出了正确的结果:

scala> counterMap.run(Counter(0)).value
res1: (Counter, scala.util.Try[Unit]) = (Counter(0),Failure(java.lang.ArithmeticException: Attempt to make count negative failed))

更新

我想出了 untilFailure方法, below , 用于运行转换序列直到它们完成或直到发生错误(以先到者为准)。我很喜欢它,因为它使用起来简单而优雅。

但是,我仍然很好奇是否有一种优雅的方式以更直接的方式将过渡链接在一起。 (例如,如果转换只是返回 Try[T] 并且没有状态的常规函数​​,那么我们可以使用 flatMap 将调用链接在一起,从而允许构建一个 for 表达式,它将成功转换的结果传递给下一个过渡。)

你能提出更好的方法吗?

最佳答案

哦!我不知道为什么我没有早点想到这一点。有时只是用更简单的术语解释你的问题会迫使你重新审视它,我想......

一种可能性是处理转换序列,以便仅在当前任务成功时才执行下一个任务。

// Run a sequence of transitions, until one fails.
def untilFailure[M](ts: List[Transition[M]]): Transition[M] = State {s =>
ts match {

// If we have an empty list, that's an error. (Cannot report a success value.)
case Nil => (s, Failure(new RuntimeException("Empty transition sequence")))

// If there's only one transition left, perform it and return the result.
case t :: Nil => t.run(s).value

// Otherwise, we have more than one transition remaining.
//
// Run the next transition. If it fails, report the failure, otherwise repeat
// for the tail.
case t :: tt => {
val r = t.run(s).value
if(r._2.isFailure) r
else untilFailure(tt).run(r._1).value
}
}
}

然后我们可以实现 counterManip作为一个序列。

val counterManip: Transition[Unit] = for {
r <- untilFailure(List(decrement, increment, increment, increment))
} yield r

这给出了正确的结果:

scala> counterManip.run(Counter(0)).value
res0: (Counter, scala.util.Try[Unit]) = (Counter(0),Failure(java.lang.ArithmeticException: Attempt to make count negative failed))

scala> counterManip.run(Counter(1)).value
res1: (Counter, scala.util.Try[Unit]) = (Counter(3),Success(()))

scala> counterManip.run(Counter(Int.MaxValue - 2)).value
res2: (Counter, scala.util.Try[Unit]) = (Counter(2147483647),Success(()))

scala> counterManip.run(Counter(Int.MaxValue - 1)).value
res3: (Counter, scala.util.Try[Unit]) = (Counter(2147483647),Failure(java.lang.ArithmeticException: Attempt to overflow counter failed))

scala> counterManip.run(Counter(Int.MaxValue)).value
res4: (Counter, scala.util.Try[Unit]) = (Counter(2147483647),Failure(java.lang.ArithmeticException: Attempt to overflow counter failed))

缺点是所有的转换都需要有一个共同的返回值(除非你对 Any 结果没意见)。

关于scala - 条件状态 monad 表达式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57257754/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com