How to tell if a Map has a default value?

default-value, dictionary, scala

Solution

You can check whether the map is an instance of `Map.WithDefault`:

implicit class EnrichedMap[K, V](m: Map[K, V]) {
  def getOrDefaultOrElse(k: K, v: => V) =
    if (m.isInstanceOf[Map.WithDefault[K, V]]) m(k) else m.getOrElse(k, v)
}

And then:

scala> val myMap = Map("a" -> "A").withDefaultValue("Z")
myMap: scala.collection.immutable.Map[String,String] = Map(a -> A)

scala> myMap.getOrDefaultOrElse("hello", "world")
res11: String = Z

scala> val myDefaultlessMap = Map("a" -> "A")
myDefaultlessMap: scala.collection.immutable.Map[String,String] = Map(a -> A)

scala> myDefaultlessMap.getOrDefaultOrElse("hello", "world")
res12: String = world

Whether this kind of reflection is any better than using exceptions for non-exceptional control flow is an open question.

Problem

Is there a way to check if a `Map` has a defined default value? What I would like is some equivalent of `myMap.getOrElse(x, y)` where if the key `x` is not in the map, - if `myMap` has a default value, return that value - else return `y` A contrived example of the issue: ``` scala> def f(m: Map[String, String]) = m.getOrElse("hello", "world") f: (m: Map[String,String])String scala> val myMap = Map("a" -> "A").withDefaultValue("Z") myMap: scala.collection.immutable.Map[String,String] = Map(a -> A) scala> f(myMap) res0: String = world ``` In this case, I want `res0` to be `"Z"` instead of `"world"`, because `myMap` was defined with that as a default value. But `getOrElse` doesn't work that way. I could use `m.apply` instead of `m.getOrElse`, but the map is not guaranteed to have a default value, so it could throw an exception (I could catch the exception, but this is nonideal). ``` scala> def f(m: Map[String, String]) = try { | m("hello") | } catch { | case e: java.util.NoSuchElementException => "world" | } f: (m: Map[String,String])String scala> val myMap = Map("a" -> "A").withDefaultValue("Z") myMap: scala.collection.immutable.Map[String,String] = Map(a -> A) scala> f(myMap) res0: String = Z scala> val mapWithNoDefault = Map("a" -> "A") mapWithNoDefault: scala.collection.immutable.Map[String,String] = Map(a -> A) scala> f(mapWithNoDefault) res1: String = world ``` The above yields the expected value but seems messy. I can't pattern match and call `apply` or `getOrElse` based on whether or not the map had a default value, because the type is the same (`scala.collection.immutable.Map[String,String]`) regardless of default-ness. Is there a way to do this that doesn't involve catching exceptions?

Original source