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!
The map doesn't contain the key . In this case, it will automatically add a key to the map with null value .
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.
We can insert new key-value pairs in a mutable map using += operator followed by new pairs to be added or updated.
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.
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.
A couple of options that are clearer:
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
}
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
}
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
}
Use Map
's getOrElse
method (assumes you throw your 404):
val path = serviceMap.getOrElse(service, throw Error404)
// Do stuff and return
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With