Frank
Frank

Reputation: 4459

Scala implicit for Option containing Map

I am trying to write the following implicit:

implicit class ExtractOrElse[K, V](o: Option[Map[K, V]]) {
  def extractOrElse(key: K)(f: => V): V = { if (o.isDefined) o.get(key) else f }
}

Which I want to use in this way:

normalizationContexts.extractOrElse(shardId)(defaultNormalizationContext)

to avoid a clunkier syntax (normalizationContexts is an Option[Map[String, NormzalitionContext]]).

Also, let me add that it is intentional that there is only one default value: it will be used if the Option isEmpty, but if the Option isDefined, then the behavior of the Map is not changed, and it will throw an exception if the key is not found - so the default value won't be used in that case, and this is all intentional.

However, I get an error when passing in None in unit tests:

assertEquals(None.extractOrElse('a')(0), 0)

results in:

Error:(165, 37) type mismatch;
 found   : Char('a')
 required: K
  assertEquals(None.extractOrElse('a')(0), 0)

I realize that None is not parametric, as it is defined as:

case object None extends Option[Nothing] {
  def isEmpty = true
  def get = throw new NoSuchElementException("None.get")

What is the best way to make this work?

Upvotes: 3

Views: 190

Answers (2)

Roberto Bonvallet
Roberto Bonvallet

Reputation: 33359

Instead of None.extractOrElse(...), try Option.empty[Map[Char, Int]].extractOrElse(...).

If you always use the same types for your test cases, you could also create a type alias in the specs class in order to reduce the clutter:

type OpMap = Option[Map[Char, Int]]
// ...
assertEquals(Option.empty[OpMap].extractOrElse('a')(0), 0)

Just in case, you can use flatMap and getOrElse to achieve the same thing without writing a new method:

val n = Option.empty[Map[String, Int]]
val s = Some(Map("x" → 1, "y" → 2))

n.flatMap(_.get("x")).getOrElse(3)  // 3
s.flatMap(_.get("x")).getOrElse(3)  // 1
s.flatMap(_.get("z")).getOrElse(3)  // 3

Upvotes: 2

Tyler
Tyler

Reputation: 18177

The type system doesn't have enough information about the types K and V. There is no way to know what the type of A would be in the case where your None was Some[A].

When I create an example with explicit types, the code works as expected:

// Like this
val e = new ExtractOrElse(Option.empty[Map[Char, Int]])
e.extractOrElse('a')(0) // Equals 0

// Or like this
val e = new ExtractOrElse[Char, Int](None)
println(e.extractOrElse('a')(0))

// Or like this
val m: Option[Map[Char, Int]] = None
val e = new ExtractOrElse(m)
println(e.extractOrElse('a')(0))

Upvotes: 1

Related Questions