Home > Mobile >  How to convert scala.collection.mutable.Map to scala.collection.immutable.Map in Kotlin?
How to convert scala.collection.mutable.Map to scala.collection.immutable.Map in Kotlin?

Time:05-09

A Scala library, I'm using in my Kotlin code, provides a function, which expects a scala.collection.immutable.Map<String!, String!>! as an argument.

To have a minimal example, let's call this function f. I'd like to the values of a given Kotlin map to it, but I'm struggling with the conversion:

fun f(x: scala.collection.immutable.Map<String, String>) {}

fun main() {
    val m = mapOf("foo" to "bar")
    val m2 = scala.jdk.CollectionConverters.MapHasAsScala(m).asScala()
    f(m2)
}

Error:

Type mismatch.
Required:
scala.collection.immutable.Map<String, String>
Found:
scala.collection.mutable.Map<String!, String!>!

I understand, I should use .toMap, but I'm not allowed to call this function without providing an ev argument:

val m2 = scala.jdk.CollectionConverters.MapHasAsScala(m).asScala().toMap<String, String>()

Error:

No value passed for parameter 'ev'

What am I missing?

CodePudding user response:

Found a solution using scala.collection.immutable.Map.from and put it into an extension function. :)

fun <A, B> Map<A, B>.toImmutableScalaMap(): scala.collection.immutable.Map<A, B> =
    scala.collection.immutable.Map.from(scala.jdk.CollectionConverters.MapHasAsScala(this).asScala())

fun main() {
    val m = mapOf("foo" to "bar")
    f(m.toImmutableScalaMap())
}

CodePudding user response:

First of all, that ev parameter is an implicit evidence, that indicates the generic type in the iterable object "namely A" is a subtype of (String, String). This can be figured out by the compiler in scala, without need to passing argument for it (it is implicit). so in Scala this works:

val myMap = ... //of type scala.collection.mutable.Map[String, String]
val immutableMap = myMap.toMap // scala.collection.immutable.Map[String, String]

But using this in Kotlin or Java needs passing an instance of type <:<[(String, String), (String, String)] (meaning a tuple of 2 strings, is a subtype of tuple of 2 strings, which is actually true). I don't think that you can want to do this, you also cannot, because of illegal inheritance. I have 2 recommendations.

1st: Create a Scala object, which can easily do this for you

object MapConvertor {
  def mutableToImmutable[K, V](mutableMap: scala.collection.mutable.Map[K, V]) = mutableMap.toMap
}

And just call this inside your Kotlin file (like MapConvertor.mutableToImmutable(...)), they are binary compatible, there would be no problems.

2nd Map in Scala, also mixes in IterableOnce trait (implements IterableOnce interface, this is more Java-ish :D). so you can cast it to the supertype. Map also has a static method that can create the map from IterableOnce. So:

scala.collection.IterableOnce<scala.Tuple2<String, String>> immutableMapIterable;
immutableMapIterable = (scala.collection.IterableOnce<scala.Tuple2<String, String>>) Test.m();
return scala.collection.immutable.Map.from(immutableMapIterable);
  • Related