1

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)?

synapse
  • 5,588
  • 6
  • 35
  • 65

2 Answers2

3

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
}
0

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 must be a subclass of (K,V). But it's not possible as tuple cannot be subclassed. Also, note that evidence are not implicit conversions, there are used to get guaranty at compile time but are not "applied".

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

Also, you can look 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]) {
    // use a self descriptive name, to avoid confusion 
    def toNameValueMap: Map[String, String] = {
        cookies.map(c => (c.name, c.value))(breakout)
    }
}

// example of use
val cookies: Seq[Cookie] = List(Cookie("chocolate", "good"), Cookie("hazelnut", "better"))

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)
Juh_
  • 14,628
  • 8
  • 59
  • 92
  • That's not what I'm asking about, see update. – synapse Oct 29 '21 at 11:41
  • I update the answer – Juh_ Oct 29 '21 at 13:10
  • If it's impossible to subclass `Tuple2[A,B]` why have `toMap` method in this particular form? I think the idea is to avoid double traversal which is done in other languages by supplying function `A => (K, V)` – synapse Oct 29 '21 at 13:16
  • I added the `breakOut` builder to avoid the second traversal – Juh_ Oct 29 '21 at 13:36
  • Did some JMH benchmarks, turns out `Vector.toMap` is so slow that it doesn't really matter if it's a single or double traversal. – synapse Oct 29 '21 at 18:58
  • IME, implicits can help improve clarity of your code. For example, the implicit builder taken by `map` methods exist to provide adaptability without needing to explicitly write code each time (note that you can, like the `breakout` i used here). And this is the most important 99% of the time. But if (micro) optimization is required, you should use old-school arrays, while-loop, and no call to (implicit) functions. But once again, most of the time having a clear code base is more important. Don't do premature optimization: http://wiki.c2.com/?PrematureOptimization – Juh_ Nov 08 '21 at 10:34
  • well, this code is pulled from a service that supposed to handle 200K requests per second so "can we do better?" is a legitimate question. Asked mostly to pique my curiosity cause conversions from vectors won't be a bottleneck, performance will be limited by other routines. – synapse Nov 08 '21 at 11:36
  • 1
    Yep, most of the time, performance bottleneck are on communication and (de)serialization. Well, IO – Juh_ Nov 08 '21 at 13:20