matanox
matanox

Reputation: 13686

Scala type disagreement between compiler and run-time?

Converting from java.util.ArrayList to scala.collection.immutable.List, the 2.10 compiler and run-time may seem to be in disagreement, about the type of val emits:

import org.ahocorasick.trie._
import scala.collection.JavaConverters._    // convert Java colllections to Scala ones

object wierd {

  val trie = new Trie

  def trieInit(patterns: List[String]) {
    trie.onlyWholeWords();
    for (pattern <- patterns)
      trie.addKeyword(pattern)
  }

  def patternTest(text : String) : List[String] = 
  {
    val emitsJ = trie.parseText(text)
    val emits = emitsJ.asScala map (i => i.getKeyword)

    println(s"converted from ${emitsJ.getClass} to ${emits.getClass}")

    //return(emits)
    return (List.empty[String])
  }

  trieInit(List("hello"))
  patternTest("hello")
}

Yields:

converted from class java.util.ArrayList to class scala.collection.immutable.$colon$colon

Now changing to return the real value by changing only the return line -

import org.ahocorasick.trie._
import scala.collection.JavaConverters._    // convert Java colllections to Scala ones

object wierd {

  val trie = new Trie

  def trieInit(patterns: List[String]) {
    trie.onlyWholeWords();
    for (pattern <- patterns)
      trie.addKeyword(pattern)
  }

  def patternTest(text : String) : List[String] = 
  {
    val emitsJ = trie.parseText(text)
    val emits = emitsJ.asScala map (i => i.getKeyword)

    println(s"converted from ${emitsJ.getClass} to ${emits.getClass}")

    return(emits)
    //return (List.empty[String])
  }

  trieInit(List("hello"))
  patternTest("hello")
}

Yields a compilation error:

[error] reproduce.scala:23: type mismatch;
[error]  found   : Iterable[String]
[error]  required: List[String]
[error]     return(emits)
[error]            ^
[error] one error found
[error] (compile:compile) Compilation failed

What would be the simple explanation for this? How would I better approach the conversion?

Upvotes: 3

Views: 234

Answers (3)

stew
stew

Reputation: 11366

the return type of trie.parseText is declared as java.util.Collection[Emit]. This isn't very specific, there are lots of possible subtypes of Collection, they aren't specifying what specific type they are going to return, it could be a TreeSet, it could be a Vector, it could be an ArrayList. But as far as the compiler is concerned, it could be anything which is a sub-type of Collection. You have inspected it at run-time and seen that for some particular input, it happened to return an ArrayList, but there is no way the compiler could know this.

When you call .asScala on this, you are using this implicit definition from JavaConverters

implicit def iterableAsScalaIterableConverter[A](i: java.lang.Iterable[A]): convert.Decorators.AsScala[Iterable[A]]

Which converts a java.lang.Itaerable into a scala.collection.Iterable.

There is no converter for Collection so you get a converter for the next most specific thing, Iterable. you call map on this Iterable and get an Iterable back.

Now, just like you have inspected the runtime value of the Collection returned from parseText and seen that it was a ArrayList you have inspected the value returned from this map operation and have seen that it is a scala.collection.immutable.List, but again, the compiler can't know this, all that it can know is that you gotten something which is a subclass of Iterable back.

Simply calling .toList on the resulting Iterable should be all you need to do.

Upvotes: 1

Noah
Noah

Reputation: 13959

So what's happening here is that the underlying object returned to you from asScala is a List but it's been downcast to an Iterable since List <: Iterable this is all fine until you want to use the Iterable as a list. The easiest option is to call toList on it.

For a little more detail you can browse the source:

  case class JCollectionWrapper[A](underlying: ju.Collection[A]) extends AbstractIterable[A] with Iterable[A] {
    def iterator = underlying.iterator
    override def size = underlying.size
    override def isEmpty = underlying.isEmpty
    def newBuilder[B] = new mutable.ArrayBuffer[B]
  }

So your call to asScala gives you back this wrapper. Next, your call to map is using this CanBuildFrom which is then used in this map operation, which finally gives you the result which happens to be a List because the builder is a ListBuffer and the result is a List, which again happens to be an Iterable because the can build from pattern downcasts to Iterable. Hope that explains everything :)

Upvotes: 1

Gangstead
Gangstead

Reputation: 4182

JavaConverters convert to the Scala collection closest to the Java collection that it has pimped. You still have to call toList to further transform it to the collection you want:

val emits = emitsJ.asScala.toList map (i => i.getKeyword)

See related: What is the difference between JavaConverters and JavaConversions in Scala?

Upvotes: 1

Related Questions