如何让for推导式求值到链式操作的最后成功步骤?是否存在对应Monad?
How to Make a For-Comprehension Return the Last Successful Step in a Chain?
Great question! You're looking for a monad that tracks the last successful value in a transformation chain, halting at the first failure and preserving that final valid result—instead of the "fail-fast" behavior you get with monads like Either or Option. Let's walk through how to model this.
Designing the LastSuccess Monad
First, let's define a monad we'll call LastSuccess[A]. It has two cases to represent its state:
InProgress[A]: The chain is still running, holding the latest successful value.FailedAt[A]: The chain hit a failure, holding the last value that succeeded before the failure.
Here's how this looks in Scala (matching your example's for-comprehension style):
sealed trait LastSuccess[+A] case class InProgress[A](value: A) extends LastSuccess[A] case class FailedAt[A](lastSuccess: A) extends LastSuccess[A] object LastSuccess { // Lift a starting value into an active, successful state def pure[A](a: A): LastSuccess[A] = InProgress(a) // FlatMap handles the core logic of preserving the last success def flatMap[A, B](fa: LastSuccess[A])(f: A => LastSuccess[B]): LastSuccess[B] = fa match { // If we're still running, execute the next transformation case InProgress(currentSuccess) => f(currentSuccess) match { // Continue the chain if the next step succeeds case InProgress(nextSuccess) => InProgress(nextSuccess) // Stop and lock in the last success if the next step fails case FailedAt(failurePointSuccess) => FailedAt(failurePointSuccess) } // If we already failed, ignore further transformations and keep our last success case failed@FailedAt(_) => failed.asInstanceOf[LastSuccess[B]] } // Map applies a function to the stored value (only modifies success states) def map[A, B](fa: LastSuccess[A])(f: A => B): LastSuccess[B] = fa match { case InProgress(a) => InProgress(f(a)) case FailedAt(a) => FailedAt(f(a)) } // Proper Monad type class instance for Scala's cats library (optional but useful) implicit val monad: cats.Monad[LastSuccess] = new cats.Monad[LastSuccess] { override def pure[A](x: A): LastSuccess[A] = LastSuccess.pure(x) override def flatMap[A, B](fa: LastSuccess[A])(f: A => LastSuccess[B]): LastSuccess[B] = LastSuccess.flatMap(fa)(f) override def tailRecM[A, B](a: A)(f: A => LastSuccess[Either[A, B]]): LastSuccess[B] = { f(a) match { case failed@FailedAt(_) => failed.asInstanceOf[LastSuccess[B]] case InProgress(Right(b)) => InProgress(b) case InProgress(Left(nextA)) => tailRecM(nextA)(f) } } } }
Applying the Monad to Your Example
Let's plug this into your transformation chain. First, define your transforms to return LastSuccess:
// Assume V is your base type def transform1(v: V): LastSuccess[V] = InProgress(v1) // Successful step def transform2(v1: V): LastSuccess[V] = InProgress(v2) // Successful step def transformThatErrors(v2: V): LastSuccess[V] = FailedAt(v2) // Fails, locks in v2 def transform4(v3: V): LastSuccess[V] = InProgress(v4) // Never executes
Now write the for-comprehension:
val result: LastSuccess[V] = for { v1 <- transform1(v) v2 <- transform2(v1) v3 <- transformThatErrors(v2) v4 <- transform4(v3) } yield v4
When you run this:
transform1andtransform2execute successfully, moving the state toInProgress(v2)transformThatErrorsreturnsFailedAt(v2), so flatMap stops the chain here- The final
resultisFailedAt(v2)—exactly the last successful value you wanted!
- If all transforms succeed, you'll get
InProgress(v4) - If the first transform fails (e.g.,
transform1(v)returnsFailedAt(v)), you'll getFailedAt(v)—the initial value, acting as the identity operation in the worst case.
Key Properties
- Non-Fail-Fast: Unlike standard error-handling monads, it preserves progress instead of discarding it on failure.
- Monad Law Compliant: This implementation satisfies left identity, right identity, and associativity laws, making it a valid monad that works seamlessly with for-comprehensions.
内容的提问来源于stack exchange,提问作者Mario Galic
相关产品推荐
相关产品推荐

