You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让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:

  1. transform1 and transform2 execute successfully, moving the state to InProgress(v2)
  2. transformThatErrors returns FailedAt(v2), so flatMap stops the chain here
  3. The final result is FailedAt(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) returns FailedAt(v)), you'll get FailedAt(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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 03:48:00