Reputation: 2576
Given a Map in Scala, I want to try a first key, if not found, try a different key, if not found again, return None. The following works as expected:
val scores: Map[String, Int] = Map("Alice" -> 10, "Bob" -> 3)
val singleGet: Option[Int] = scores.get("NotAKey")
println(singleGet) // None
val doubleGet = scores.getOrElse("NotAKey", scores.get("NotAKeyAgain")) // works ok if no type
println(doubleGet) // None
But if I put a type for doubleGet, it errors:
val doubleGet: Option[Int] = scores.getOrElse("NotAKey", scores.get("NotAKeyAgain")) // ERROR
"Expression of type Any doesn't conform to expected type Option[Int]"
So what would be the best way to do this?
Upvotes: 0
Views: 871
Reputation: 22840
You have a good intuition on what you want to do, which is:
"test one key, if that does not exists, then check the second one".
Also, you are correct on the return type, because you have not provided any default, and there isn't a guarantee of that either key should exists.
But, your problem is that scores.getOrElse("NotAKey", scores.get("NotAKeyAgain"))
will return Any.
Why? Because getOrElse
returns the LUB (least upper bound) of both alternatives. In this case default is an Option[Int]
but the type of a successful retrieval of the first key, is Int
.
What you really need is a way to compose two Options, where the second one is used in the case the first one does not exists. And that is exactly what orElse
does.
You can create some extension methods over a Map to make the usage more easy.
implicit class MapOps[K, V](private val map: Map[K, V]) extends AnyVal {
def doubleGet(key1: K, key2: K): Option[V] =
map.get(key1) orElse map.get(key2)
def doubleGetOrElse[V1 >: V](key1: K, key2: K)(default: => V1): V1 =
(map.get(key1) orElse map.get(key2)).getOrElse(default)
def multiGet(keys: K*): Option[V] =
keys.iterator.map(key => map.get(key)).foldLeft(Option.empty[V])(_ orElse _)
def multiGetOrElse[V1 >: V](keys: K*)(default: => V1): V1 =
keys.iterator.map(key => map.get(key)).foldLeft(Option.empty[V])(_ orElse _).getOrElse(default)
}
val scores: Map[String, Int] = Map("Alice" -> 10, "Bob" -> 3)
scores.doubleGet("A", "B") // res: Option[Int] = None
scores.multiGet("A", "Alice", "B") // res: Option[Int] = Some(10)
Upvotes: 2