1

In simple example

trait Holder[T] {def value: T}

class LongHolder(val value: Long) extends Holder[Long]    
class StringHolder(val value: String) extends Holder[String]

val r1 = List(new LongHolder(1), new StringHolder("a"))    
val h0 = r1(0)    
val h1 = h0.value


scala> r1: List[Holder[_ >: String with Long]] = List(LongHolder@5761bff, StringHolder@d280526)
scala> h0: Holder[_ >: String with Long] = LongHolder@5761bff
scala> h1: Any = 1

I can not understand why h1: Any = 1 in LongHolder@5761bff, but not h1: Long = 1

without List type is not lost

val h0 = new LongHolder(1)
val h1 = h0.value

scala> h0: LongHolder = LongHolder@7b2e3720
scala> h1: Long = 1
andrey.ladniy
  • 1,664
  • 1
  • 11
  • 27

3 Answers3

3

It happens because Scala has no union types like type T = String | Long. Your T from List[T] should have some concrete type, like String, Long or something, and you can't create list of several types. Every element in the list must have same T type, which makes many operations (like flatMap or filter) to be possible:

 scala> val a: (Int, Int, String, String, Int) = parseSomething() // tuple may be seen as List with heterogenous elements
 a: (Int, Int, String, String, Int) = (5, 6, "a", "b", 7)

 scala> val b = a.filter(_ != 6) //how compiler would know that `a._2 == 6` to infer (Int, String, String, Int) ?
 b: (???) = (5, "a", "b", 7)

So T should be same for all elements. When the compiler sees several parameters that have several types, it's trying to find the nearest common type:

def ff[T](a: T, b: T): T = a

trait A
case class A1() extends A
case class A2() extends A 

scala> ff(A1(), A2())
res20: A = A1() //A is the nearest common type

Generics are a little bit more interesting. The compiler may infer existential type for them M[_ >: A1 with A2]:

scala> ff(new LongHolder(1), new StringHolder("a"))
res23: Holder[_ >: String with Long] = LongHolder@a5e201f

It means Holder[T] forSome {type T >: String with Long}, which means that Holder is defined for some T (such T should exist but not necessary for all holders), that >: String with Long. Simply saying compiler asks "Hey! We need some T that bigger than Int with Long here" (just imagine types as boxes to put something in, and compiler is just a "foreman")

Finally Any is smaller (closest) type to fit in (it fits as it can be bigger than String with Long), so result is becoming Any, as value itself can not be existential in Scala.

P.S. Shapeless HList actually does what you want.

Community
  • 1
  • 1
dk14
  • 22,206
  • 4
  • 51
  • 88
2

The elements of a List need to be of one type. The compiler will do its best to determine the type of the list if it is not explicitly provided. You have a list that includes a LongHolder and a StringHolder, so the compiler will try to use the least common type of those types as the type of the list. Since both those types extend Holder, it will try to use that as the type. In order to determine the type to use for the generic parameter of Holder, the compiler need to find the least common type of String and Long, which is the synthetic type _ with String with Long. Since no nontrivial types extends both String and Long, the only type it can use is Any, which all types extend.

Ben Reich
  • 16,222
  • 2
  • 38
  • 59
1

The explanation of dk14 is correct. But if your goal is to save information about types in runtime you can use Scala reflection. I modified example in docs a little bit to demonstrate that:

def mkList[T : ClassTag](elems: T*) = List[T](elems: _*)

val r1 = mkList(new LongHolder(1L), new StringHolder("a"))
val h0 = r1(0)
h0.value.getClass

scala> h0: Holder[_1] = LongHolder@6618cd9b
scala> res0: Class[?0] = class java.lang.Long
Community
  • 1
  • 1
Nikita
  • 4,435
  • 3
  • 24
  • 44
  • 1
    type erasure has nothing to do with this as type becomes "lost" in compile time - not in runtime – dk14 Apr 11 '15 at 10:46
  • 1
    You're right and your explanation is pretty clear about that. I edited my answer and hope it still contains some useful information. – Nikita Apr 11 '15 at 11:02