问题描述:

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!

网友答案:

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
    
网友答案:

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.

网友答案:

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
}
网友答案:

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.

网友答案:

Lift the Map to a partial function defined only for mapped keys, namely for instance, for a given map

val a = Map( 1->2, 3->4)

we can lift a with

a.lift
Int => Option[Int] = <function1>

and so

a.lift(1)
Option[Int] = Some(2)

a.lift(0)
Option[Int] = None

Consider then a for comprehension for instance like this,

val ok = for { v <- a.lift(1); res <- Some(awesome_stuff(v)) }
         yield res

ok getOrElse report404

If v or res do not succeed (namely, that are None), then ok is None.

相关阅读:
Top