Reputation: 632
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
Reputation: 1349
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.
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