amandasystems
amandasystems

Reputation: 111

Is there a way to chain `Try` as a monad while also not catching exceptions raised along the chain?

The Scala Try construct together with its flatMap does not work as I would expect or want it to. The TL;DR is that I want to do a series of operations that can fail in two ways: either by raising an exception, which should be promoted and caught higher up in the call stack, or by returning Failure, as the failure must logically be handled in different parts of the program.

I would expect something like this to do the trick:

def firstStepSucceeds(): Try[Int] = Try {
  1
}

def secondStepThrows(input: Int) = {
  throw new Exception("Exception thrown in second step")
}

// I expect this to propagate the exception thrown in secondStepThrows
firstStepSucceeds() flatMap (secondStepThrows _)

(Full Scastie with example)

However, in this case, the flatMap() call actually implicitly catches the uncaught exception thrown by secondStepThrows, which is not what I want (which is why I left out the Try block). Is there a way to get the same behaviour without the implicit exception-catching?

Upvotes: 1

Views: 724

Answers (3)

Karl Bielefeldt
Karl Bielefeldt

Reputation: 49028

What happens in a Try should stay in a Try. Most Scala programmers would be very surprised if a function returning a Try also sometimes threw an exception.

The typical pattern if you want to handle exceptions in different places is to differentiate by the type of the exception. So

val partiallyRecoveredTry = originalTry.recover{
  case _: SecondStepException => "second step had an exception"
}
// Further up the call stack
partiallyRecoveredTry.getOrElse("first step had an exception")

Upvotes: 3

amandasystems
amandasystems

Reputation: 111

I did some further experimentation, and what I ended up with was this reimplementation of Try as (the now right-biased and hence monadic) Either:

object CatchAll {
  def apply[SomeType](block: => SomeType) = try { Right(block) }
  catch { case e: Throwable => Left(e) }
}

def firstStepSucceeds() = CatchAll {
  1
}

def firstStepFails() = CatchAll {
  throw new Exception("First step failed")
}

def secondStepSucceeds(input: Int) = CatchAll {
  input + 1
}

def secondStepFails(input: Int) = CatchAll {
  throw new Exception("Second step failed in try block!")
}

def secondStepThrows(input: Int) = {
  throw new Exception("Second step failed unexpectedly!")
}

firstStepSucceeds() flatMap (secondStepSucceeds _)
firstStepFails() flatMap (secondStepSucceeds _)
firstStepSucceeds() flatMap (secondStepFails _)

// This now throws an exception as expected
//firstStepSucceeds() flatMap (secondStepThrows _)

Upvotes: 0

volia17
volia17

Reputation: 938

Try.flatMap() did not caught exceptions implicitely, it is the essence of Try. When you use it, it is very explicit, and that's the goal.

I don't really understand what you want, but is something like that is possible for you ?

try {
  val first = firstStepSucceeds()
  val second = first.map(secondStepThrows).get
  val third = secondStepFails(second)
  // ...
}
catch {
  case e: Exception => ???
}

Upvotes: 0

Related Questions