-1

I am inside a for comprehension where we compose multiple operations together. When each step is completed, I need to either throw an error using MonadError.raiseError or carry on to the next step if valid.

def myFunc[F[_]: Monad](input: Input)(implicit err: MonadError) = {
  for {
    stepAResult <- runStepA(input)
    if (stepAResult.isInstanceOf[Invalid[_]) {
      err.raiseError(new Throwable("error 1"))
    } else {
       stepBResult<- runStepB(stepAResult.toOption.get, input)
       if (stepBResult.isInstanceOf[Invalid[_]]) {
          err.raiseError(new Throwable("error 2"))
       } else {
        stepCResult <- runStepC(stepBResult.toOption.get)
        // check for invalid here again.
      }
    }
  }
}

This does not compile. I need to understand if there is a way of making this work.

vamsiampolu
  • 6,328
  • 19
  • 82
  • 183
  • 1
    Have you tried using `map`/`flatMap` directly rather than using `for`? And you should probably be using `match` rather than `isInstanceOf`. – Tim Feb 28 '19 at 08:20
  • Can you show me an example of how to do this in this case? I tried several different ways but the code would not compile. – vamsiampolu Mar 01 '19 at 00:49
  • You would need to give more details on the types before we can make any concrete suggestions, but remember that `for ( a <- f(x) ) ...` is roughly the same as `f(x).map(a => ...` – Tim Mar 01 '19 at 09:24

2 Answers2

0

Assuming you have control over "runStep" functions, I'd suggest you to modify them to return an Either[Invalid, _]. This way, you can compose them in a single for comprehension like this (I have used Throwable instead of Invalid, but the idea is the same):

def raiseError(e: Throwable): Unit = println(e.getMessage)

def firstMethod(v: Int): Either[Throwable, Int] = Right(v * 2)    
def secondMethod(v: Int) : Either[Throwable, Int] = Try(v / 0).toEither    
def thirdMethod(v: Int): Either[Throwable, Int] = Right(v * 2)

val x = for {
  first <- firstMethod(5)
  second <- secondMethod(first)
  third <- thirdMethod(second)
} yield third

x match {
  case Left(err) => raiseError(err)
  case Right(result) => println(result)
}

Here, secondMethod throws an exception, so thirdMethod is never executed, which is what you're trying to do

Rodrigo Vedovato
  • 1,008
  • 6
  • 11
0

For comprehensions are just syntax sugar for a sequence of flatMap calls. Because of this, not just any Scala code can go inside your for comprehension. The following is one of the things you are trying to do that is not legal Scala:

//This does not compile because normal if-else statements are not valid inside a for comprehension
object Example1 {
  def f(o: Option[Int]): Option[Int] = for {
    x <- o
    if (x < 0) "return some value"
    else { //attempting to continue the for comprehension
      y <- o
    }
  } yield ??? //what would we yield here?
}

The if keyword in a for comprehension is used for guards:

object Example2 {
  def f(o: Option[Int]): Option[Int] = for {
    x <- o
    if x >= 0
  } yield x

  //f and f2 are equivalent functions
  def f2(l: Option[Int]): Option[Int] = l.filter(_ >= 0)
}

But that doesn't look like what you want. It looks like you're trying to keep track of exceptions as you run each step. The Try Monad does exactly this and can be used in a for comprehension. Note the equivalent Scala code that uses flatMap calls instead of a for comprehension. I recommend writing the function with all the nested flatMap calls before trying to convert to the prettier for comprehension syntax if you're getting stuck. Checkout this answer for some examples of how to do this.

// myFunc1 is equiv to myFunc2 is equiv to myFunc3
// they only differ in syntax
object Example3 {
  import scala.util.Try

  def runStepA[A](in: A): Try[A] = ???
  def runStepB[A](in: A): Try[A] = ???
  def runStepC[A](in: A): Try[A] = ???

  def myFunc1[A](input: A): Try[A] = for {
    nonErrorResultA <- runStepA(input)
    nonErrorResultB <- runStepB(nonErrorResultA)
    nonErrorResultC <- runStepC(nonErrorResultB)
  } yield nonErrorResultC

  def myFunc2[A](input: A): Try[A] =
    runStepA(input).flatMap {
      nonErrorResultA => runStepA(nonErrorResultA).flatMap {
        nonErrorResultB => runStepB(nonErrorResultB).flatMap {
          nonErrorResultC => runStepC(nonErrorResultC)
        }
      }
    }

  def myFunc3[A](input: A): Try[A] =
    runStepA(input).flatMap {
      runStepA(_).flatMap {
        runStepB(_).flatMap {
          runStepC
        }
      }
    }
}
codenoodle
  • 982
  • 6
  • 19