Scala: A throw error against a return. Give it a try?

Should the Scala API ideally throw exceptions or return a Try value? Is there an official guide on this?

def doSomethingMayThrow(): A = ??? def doSomethingWontThrow(): Try[A] = ??? 
+6
source share
2 answers

Never throw exceptions for recoverable errors.

Returning the appropriate data structure representing a possible failure (a Future , a Try , a Either , etc.) is always preferable to throwing exceptions in the wild. He will inform the caller of the possibility of failure, and this will force them to control it.

Exceptions should be thrown only for fatal errors, such as equipment failures, etc.

Here is an example:

 def throwOnOdds(x: Int): Int = if (x % 2 == 0) x / 2 else throw new Exception("foo") val x = throwOnOdds(41) + 2 // compiles and explodes at runtime 

let's do it better

 def throwOnOdds(x: Int): Try[Int] = if (x % 2 == 0) Success(x / 2) else Failure(new Exception("foo")) val x = throwOnOdds(41) + 2 // doesn't compile 

A failure failure results in a compile-time error, which is much better than runtime. Here's how to handle it.

 throwOnOdds(41) match { case Success(n) => // do something with n case Failure(e) => // handle exception } 
+11
source

See monadic datatypes. Using monadic data types is much more expressive and understandable than throwing exceptions and it would be the preferred way to declaratively handle all cases without unclear side effects. http://en.wikipedia.org/wiki/Monad_(functional_programming)

The advantage of using failure with success and using a map and flatMap to express a β€œhappy journey” is that exceptions / failures become apparent in the chain.

If you can tell if you call doSomethingMayThrow there may be a side effect (like throwing an exception), it is very clear if you are using a monadic data type as a result.

This will help to look at the real world. I will use this as this is what I have been working on recently:

Consider the monadic future in a caching scenario - if the cache returns a result, you can process the result. If the cache does not return a result, we can go to the actual service from which we are trying to cache the results, and we can express it very explicitly without any unclear implied side effects, such as an exception:

Here recoverWith is similar to flatMap in case of an error (instead of a failure) (return the Future [Model]). recovery is like a map in case of an error (return the model to the future instead of failure). Then the card takes any received model and processes it - all cases are clearly defined in the code, so there is clarity of all cases and conditions in one expression.

 (userCacheActor ? GetUserModel(id="abc")) .recoverWith(userServiceActor ? GetUserModel(id="abc")) .recover(new ErrorModel(errorCode=100, body="service error") .map(x: Response => createHttpResponseFromModel(x)) def createHttpResponseFromModel(x: Model) => x match { case model: ErrorModel => ??? case model: UserModel => ??? } 

Again, everything is clearly indicated - what to do with the cache failure, what to do if the service cannot respond in this scenario, what to do with the result model at the end of all processing in any case.

Often flatMap talk about the "plumbing" of the monad or the "plumber" of a happy journey. flatMap allows you to take another Monad and return it. This way, you can β€œtry” a few scripts and write the code for the happy journey, collecting all the errors at the end.

 scala> Option("Something").flatMap(x => Option( x + " SomethingElse")) .flatMap(x => None).getOrElse("encountered None somewhere") res1: String = encountered None somewhere scala> scala.util.Try("tried") .flatMap(x => scala.util.Try( x + " triedSomethingElse")) .flatMap(x => scala.util.Try{throw new Exception("Oops")}) .getOrElse("encountered exception") res2: String = encountered exception 
+2
source

Source: https://habr.com/ru/post/974614/


All Articles