Home > Software engineering >  Implicit converter for toMap
Implicit converter for toMap

Time:10-30

How to create an implicit converter so this code

case class Cookie(name: String, value: String)

val cookies: Seq[Cookie] = ???

val cookieMap: Map[String, String] = cookies.toMap

wil work? How to define implicit ev: A <:< (K, V)?

CodePudding user response:

You can just do:
cookies.iterator.map(c => c.name -> c.value).toMap
To do the conversion in a single iteration. You may even hide that behind an extension method or something if you please.

Or you can provide an
implicit def Cookie2Tuple2(c: Cookie): (String, String) = c.name -> c.value
But that may blow up in other unexpected parts of the code.

I personally would define my own.

final case class Cookie(underlying: List[Cookie]) {
  def toMap: Map[String, String] =
    underlying.iterator.map(c => c.name -> c.value).toMap
}

CodePudding user response:

From scala doc:

An instance of A <:< B witnesses that A is a subtype of B. Requiring an implicit argument of the type A <:< B encodes the generalized constraint A <: B.

That means that evidence Cookie <:< (K, V) means Cookie is a subclass of (K,V), which is not possible. Evidence are not implicit conversions.

(you can also read: https://stackoverflow.com/a/3427759/1206998)

Then, I looked at the implementation of toMap in IterableOnce

def toMap[K, V](implicit ev: A <:< (K, V)): immutable.Map[K, V] =
    immutable.Map.from(this.asInstanceOf[IterableOnce[(K, V)]])

as you can see, it cast the iterable to an iterableOnce of tuple, no conversion of the collection's item is performed. Thus, entries should be effectively (K,V)


So the only thing you can do is add an implicit conversion from Seq[Cookie] to Map(String, String]

import scala.collection.breakOut // to directly convert to Map

case class Cookie(name: String, value: String)

implicit class Cookies(cookies: Seq[Cookie]) {
    def toNameValueMap: Map[String, String] = {
        cookies.map(c => (c.name, c.value))(breakout)
    }
}

val cookies: Seq[Cookie] = List(Cookie("chocolate", "1.2"), Cookie("peanut", "1.6"))

val cookieMap: Map[String, String] = cookies.toNameValueMap

And for the fun of it, here is the generic conversion method that correspond to what you expected toMap to be able to do:

implicit class SeqConversion[A](seq: Seq[A]) {
    def convertToMap[K,V](getKey: A => K, getValue: A => V): Map[K,V] ={
        seq.map(a => (getKey(a), getValue(a)))(breakOut)
    }
}
cookies.convertToMap(_.name, _.value)
  • Related