0
votes

In this answer it says...

  // Universal types let you write things like:

  def combine[T](source: List[T], dest: List[T]): List[T] = {
    source ++ dest
  }

But I did not understand the explanation.

Can someone explain the difference between the universal type example above and some of the similar examples (which contain existential types) below?

  def combine2[_](source: List[_], dest: List[_]): List[_] = {
    source ++ dest
  }


  def combine3(source: List[_], dest: List[_]): List[_] = {
    source ++ dest
  }

  def combine4[A, B](source: List[A], dest: List[B]): List[_] = {
    source ++ dest
  }

  def combine5[T](source: List[T], dest: List[T] forSome {type T}): List[T] forSome {type T} = {
    source ++ dest
  }

Java for good measure...

public static List<?> combine6(final List<?> source, final List<?> dest) {
    return source;
}

// Why doesn't this compile? 
public static <T> List<T> combine7(final List<?> source, final List<?> dest) {
    return source;
}

Also, if I provide typetags, does that in any way replace the need for existential types?

  def combineTypetag[A, B, C](source: List[A], dest: List[B]) 
  (implicit tagA: TypeTag[A], tagB: TypeTag[B], tagC: TypeTag[C]): List[C] = {
    source ++ dest
  }
1

1 Answers

3
votes

combine says that if you have two lists with the same element type, you get the same type back, e.g.:

val list1: List[Int] = ...
val list2: List[Int] = ...
val list3 = combine(list1, list2) // also List[Int]
val x = list3.head // Int
val y = x + x // Int

combine can also be used with lists of different types, and return the most precise common type:

val list1: List[FileInputStream] = ...
val list2: List[ByteArrayInputStream] = ...
val list3 = combine(list1, list2) // List[InputStream]

All the other options return List[T] forSome {type T}, i.e. list of some unknown type (List[_] is just a short way to write this):

val list1: List[Int] = ...
val list2: List[Int] = ...
val list4 = combine2(list1, list2) // List[_]
val z = list4.head // Any
val w = z + z // doesn't compile

So they simply lose the type information. Only use existential types when you can't be any more precise.

Why doesn't this compile?

If it did, what would you expect to happen here:

List<?> list = Arrays.asList("a", "b");
List<Integer> list2 = <Integer>combine7(list, list);

?

Also, if I provide typetags, does that in any way replace the need for existential types?

There is no need for existential types here, but when there is, type tags won't help: the compiler by definition can only insert type tags when it knows what the static types are, so that existential types aren't needed.