Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to gracefully handle "Key not found" In a Scala map?

In summary, I need a functional way to do one thing if a value was found in a Map and Do another thing if it was not found. Note that I am NOT interested in the value returned but the action done.

Read on for the details.

I have a map of service name (friendly) to a part of a URL PATH (not easy to remember). Here is the initialization.

val serviceMap = Map("read" -> "cryptic-read-path",
  "save" -> "cryptic-save-path", "county" -> "cryptic-zip-code-to-county-service.");

All the alternatives I am aware of lead to an if statement with a loaded then part, but a simple, 404 else part. Here are some that I thought about

if (serviceMap.contains(service)) {
    //Do stuff
} else {
    //Issue a 404
}

It's equivalent with predicate reversed

if (!serviceMap.contains(key)) //issue a 404
//Do stuff

Both approaches above require me to check-then-get.

Another one (discouraged by Option documentation )

  serviceMap.get(service) match {
  case _ : Option[String]=> //Do stuff
  case _  => //issue 404
}

Yet a third one

  serviceMap.get(service).forEach {//Dostuff and return, since it's just one element }
  //issue 404 if you are here.

Per Option documentation, I am encouraged to treat Option as a collection, so the last alternative seems better, but the use of forEach seems odd to change execution flow after the loop.

It is my inexperience, but all of these look too verbose and inappropriate to me. Can you guys give me a better option or comment on the approaches? Thanks in anticipation!

like image 695
Prashant Avatar asked Sep 02 '15 18:09

Prashant


People also ask

What happens if key not found in map?

The map doesn't contain the key . In this case, it will automatically add a key to the map with null value .

How do you check if a key exists in a map Scala?

The contains() method of Scala is equivalent to the isDefinedAt method of Scala but the only difference is that isDefinedAt is observed on all the PartialFunction classes while contains is clearly defined to the Map interface of Scala. It checks whether the stated map contains a binding for a key or not.

How do I add a key-value pair to a Scala map?

We can insert new key-value pairs in a mutable map using += operator followed by new pairs to be added or updated.

Can Scala map have duplicate keys?

Scala Map is a collection of Key-value pair. A map cannot have duplicate keys but different keys can have same values i.e keys are unique whereas values can be duplicate.


4 Answers

I would think the idiomatic way is to use map and getOrElse:

val result = serviceMap.get(service).map { url =>
  // do something with url
} getOrElse {
  // not found
}

In general, don't think about Option as a one-element collection to loop over (which isn't the functional approach) but to transform into something else.

like image 67
Mikesname Avatar answered Oct 17 '22 13:10

Mikesname


A couple of options that are clearer:

  1. match with unapply (which avoids the type-erasure problems of _ : Option[CantTellWhatThisIsAtRuntime]):

    serviceMap.get(service) match {
      case Some(path) => // Do stuff and return
      case _ => // 404
    }
    
  2. Use a fold to extract the value (this assumes that you can type your 404 appropriately):

    serviceMap.get(service).fold(/* 404 */) { path =>
      // Do stuff and return
    }
    
  3. Use map + getOrElse to transform the path into a response or return / throw a 404 (hat tip to Mikesname):

    serviceMap.get(service).map { path =>
      // Do stuff and return
    } getOrElse {
      // 404
    }
    
  4. Use Map's getOrElse method (assumes you throw your 404):

    val path = serviceMap.getOrElse(service, throw Error404)
    // Do stuff and return
    
like image 33
Sean Vieira Avatar answered Oct 17 '22 13:10

Sean Vieira


Personally, I would do it using pattern matching. It makes it very readable.

serviceMap.get(service) match {
  case Some(s) => println("Here's my string from the map! " + s)
  case _ => //issue 404
}

Also, code can be easily modified then. For instance, if at some point in the future you'll need to do stuff if specifically for the value that map to "cryptic-read-path", you can do this:

serviceMap.get(service) match {
  case Some("cryptic-read-path") => //do stuff that is specific for cryptic read path
  case Some(s) => println("Here's my string from the map! " + s)
  case _ => //issue 404
}

For the third option, you propose returning inside a foreach. That would cause a non-local return, which is not efficient. Read more about it here

Edit: You also can do this if you don't need the result of the get call:

serviceMap.get(service) match {
  case Some(_) => //do stuff
  case _ => //issue 404
}
like image 43
ilinum Avatar answered Oct 17 '22 13:10

ilinum


Your goal is perform different actions based on the value existing or not. While pattern matching is a less-idiomatic way to convert an Option to a value, I would say it is wholly appropriate as a branching mechanism to choose between multiple actions. For that reason I would recommend using the match alternative you proposed.

If, however, you disagree that it is appropriate, consider looking at your problem as one of converting an Option into an action:

 val response = serviceMap.get(service).map { value =>
   // do stuff to convert into a 200 response
 }.getOrElse {
   // create 404 response
 }

The map converts the any Some[String] into a Some[Response], while keeping keeping a None. Now that you have an Option[Response] you can use getOrElse to substitute your 404 response for the possibility of a None.

like image 1
Arne Claassen Avatar answered Oct 17 '22 11:10

Arne Claassen