Denis Loshkarev
Denis Loshkarev

Reputation: 632

How scala determines implicit type parameters for TreeSet constructor

I'm doing excersises from 'Scala for the impatient' book and stumbled across this magic. I have a simple scala object for executing code:

object Main {

  def main(args: Array[String]): Unit = {
    println(charIndex("Missisipi"))
    //var s = scala.collection.immutable.SortedSet[Int]()
  }

  def charIndex(s: String) : scala.collection.mutable.Map[Char, scala.collection.mutable.TreeSet[Int]] = {
    val m = scala.collection.mutable.Map[Char, scala.collection.mutable.TreeSet[Int]]()
    s.zipWithIndex.foreach{
      case (c, i) => m += c -> (m.getOrElse(c, scala.collection.mutable.TreeSet()) += i)
    }
    m
  }
}

No matter how it looks like (I did it the ugliest way), but in this case it doesn't compile with error:

Error:(14, 80) diverging implicit expansion for type scala.math.Ordering[T1] starting with method Tuple9 in object Ordering case (c, i) => m += c -> (m.getOrElse(c, scala.collection.mutable.TreeSet()) += i) Error:(14, 80) not enough arguments for method apply: (implicit ord: scala.math.Ordering[A])scala.collection.mutable.TreeSet[A] in class SortedSetFactory. Unspecified value parameter ord. case (c, i) => m += c -> (m.getOrElse(c, scala.collection.mutable.TreeSet()) += i)

But if you uncomment line in main method it will be compiled successfully. Why is this happenning?

Upvotes: 3

Views: 202

Answers (1)

Denis Iakunchikov
Denis Iakunchikov

Reputation: 1349

Core of a problem

If you run this example with -Xlog-implicits (for sbt scalacOptions := Seq( "-Xlog-implicits" )) you will see a lot of [info] messages like this

math.this.Ordering.Tuple6 is not a valid implicit value for scala.math.Ordering[A] because: [info] diverging implicit expansion for type scala.math.Ordering[T1]

this goes for basically every type in math.this.Ordering from simple like Boolean to Iterables and Options.

It tells us that compiler frantically trying to guess what type of TreeSet you are trying to construct when getOrElse fails. In your example it's just a TreeSet without any [explicit] typing so compiler has to find an implicit type to bind new TreeSet to. By specifying Int in TreeSet constructor you will eliminate search for implicits and code will work without any magic.

case ( c, i ) =>  m += c -> ( m.getOrElse( c, scala.collection.mutable.TreeSet[Int]() ) += i)

Now, to the main point.

How compiler searches for implicits aka why is this magic worked

You specified that m is a Map to TreeSet[Int], that's great but .getOrElse will return a default value for m when it will fail to get a first character. To construct that default value it will use definition provided by you

scala.collection.mutable.TreeSet()

In the docs for .getOrElse we can see that for default value compiler will use a result yielded from of a second parameter computation. Constructor of a TreeSet need to know what type of values you want to keep in that set and if it fails to see explicit definition it will look for implicits.

new TreeSet()(implicit ord: Ordering[A])

As I mentioned in the begining it will bruteforce all the possible types in Ordering because, you know, it has to be ordering-oriented type but the tricky part is it doesn't know what type it actually needs. Compiler has to construct a new TreeSet before it could += i to it. It can't just look forward, see that i is Int, construct a TreeSet[Int] and get back to add i to it. That would be an actual magic.

There is a great post on how does scala determines types for implicit parameters. Note that SortedSet is connected throught traits to TreeSet so it fails into category "some part of T". In this particular case compiler will see an explicitly defined SortedTree[Int] and since there is no other option will use Int as implicit type for TreeSet.

Upvotes: 3

Related Questions