Search code examples
scalagenericstype-parameter

Scala typesystem. "Deferred" type parameter. Why this works?


I have been writing some code and it has lead me to:

trait SplitStrategy[E] {
  def apply(seq: Seq[E]): Seq[(Int, Seq[E])]
}

object SplitByConsecutiveElements {
  def apply[E](consecutiveValue: Seq[E] => E): SplitByConsecutiveElements[E] = new SplitByConsecutiveElements(consecutiveValue)
  def withConsecutiveValueAsTheHighestCount[E]: SplitByConsecutiveElements[E] = {
    val consecutiveValue: Seq[E] => E = seq => {
      seq.foldLeft(Map.empty[E, Int].withDefaultValue(0)) {
        case (m, v) => m.updated(v, m(v) + 1)
      }.maxBy(_._2)._1
    }
    SplitByConsecutiveElements(consecutiveValue)
  }

  def main(args: Array[String]): Unit = {
    println(SplitByConsecutiveElements.withConsecutiveValueAsTheHighestCount.apply(Seq(1, 1, 2, 1, 2)))
  }

}

class SplitByConsecutiveElements[E](val consecutiveValue: Seq[E] => E) extends SplitStrategy[E] {
  override def apply(seq: Seq[E]): Seq[(Int, Seq[E])] = splitSequenceBySequenceOfElements(seq, consecutiveValue(seq))
  private def splitSequenceBySequenceOfElements[E](seq: Seq[E], consecutiveValue: E): Seq[(Int, Seq[E])] = {
    // This is just a dummy operation, not the real algorithm
    Seq((0, seq.filter(consecutiveValue == _)))
  }
}

If you look at the "main" method, you can see I call SplitByConsecutiveElements.withConsecutiveValueAsTheHighestCount and then I apply it to a Sequence of Int. At first, I though it would not compile. But it does compile and works properly. My rationale for it to not work is that when calling SplitByConsecutiveElements.withConsecutiveValueAsTheHighestCount I am creating a SplitByConsecutiveElements[E] of unknown type parameter E. Then I apply SplitByConsecutiveElements[E].someMethod, where E is known, but I have already created an instance of SplitByConsecutiveElements[E] and not of SplitByConsecutiveElements[Int], for example. Why does this work? It is like the class transofrms itself at compile time?

My head is just a mess right now, I hope I conveyed my concern well.


Solution

  • There are two reasons why it works:

    1) Generics on JVM are erased so it's possible to create an instance of SplitStrategy[E] without knowing E. So no need to "transform the class at compile time".

    2) Even if we were on a platform where generic types are not erased (say, CLR for C#) this code still could work.

    Compiler tries to infer E in SplitStrategy[E] based on the context to make the entire expression valid. In you case it successfully guesses that E is Int.