Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala most elegant way to handle option and throw exception from Scala Map

I have a map and want to:

  • retrieve value without handling Option
  • log a message when there is no such the key.
  • nice to return a default value ( in addition to log a message) when the key is not present. This is optional because when the code fails here, it should not continue further.

I have several ways of doing it

val map: Map[String, Int] // defined as such for simplification

// 1 short but confusing with error handling
def getValue(key: String): Int = {
    map.getOrElse(key, scala.sys.error(s"No key '$key' found"))
}

// in case you don't know scala.sys.error 
package object sys {
 def error(message: String): Nothing = throw new RuntimeException(message)
}

// 2 verbose
def getValue(key: String): Int = {
    try {
      map(key)
    } catch {
      case _: Throwable => scala.sys.error(s"No key '$key' found")
    }
}

// 3 Try and pattern matching
import scala.util.{Failure, Success, Try}

def getValue(key: String): Int = {
    Try(map(key)) match{
      case Success(value) => value
      case Failure(ex) => sys.error(s"No key '$key' found ${ex.getMessage}")
    }
}

Others solutions are welcomed. I'm looking for conciseness without being cryptic. Using standard scala ( no need for Scalaz, Shapeless ... )

Inspirations :

  • https://alvinalexander.com/scala/how-to-access-map-values-getorelse-scala-cookbook
  • http://danielwestheide.com/blog/2012/12/26/the-neophytes-guide-to-scala-part-6-error-handling-with-try.html
like image 345
Raymond Chenon Avatar asked Mar 13 '18 11:03

Raymond Chenon


1 Answers

The most elegant way to throw an error is your (1):

map.getOrElse(key, throw /* some Exception */)

The 2nd and 3rd options should not be used: You know which actual error can happen: the map doesn't contain the key. So wrapping it in a try, or Try, is more work than necessary. And worst, it will catch other exceptions that are not meant to be. In particular Fatal exception that should not be caught.


But, the real most elegant way to manage exceptions in scala is to actually track them with types.

A simple generic way (but sometime too generic) is to use Try. As soon as your code might fail, everything is wrapped in Try and later code is called in map and flatMap (you can use for-comprehension to make it more readable)

A more specific way is to use Either (from scala) or \/ from scalaz and explicit the type of error you are managing, such as \/(MissingData, String) for some MissingData class you've made. Eg:

import scalaz.\/
import scalaz.syntax.either._

sealed trait KnownException
case class MissingData(message: String) extends KnownException

// type alias used for readability
type Result[A] = \/[KnownException, A]

// retrieve a param, or a MissingData instance
def getParam(param: Map[String, Int], key: String): Result[Int] = param.get(key) match {
  case None => MissingData(s"no param for key $key").left
  case Some(v) => v.right
}

// example on how to combine multiple \/
// using for-comprehension
def computeAPlusB(param: Map[String, Int]): Result[Int] = for {
  paramA <- getParam(param, "a")
  paramB <- getParam(param, "b")
} yield paramA + paramB
like image 192
Juh_ Avatar answered Nov 15 '22 11:11

Juh_