Problem with wildcard type argument

Is there any good reason why the following should not work?

class Box[A]

def foo[A](xs: List[Box[A]]) = xs

def boxes: List[Box[?]] = ???

val boxes2 = foo(boxes)

I get:

Found:    List[Box[?]]
Required: List[Box[A]]

I’ll have a crack at this: warning, grug analysis follows…

Try this instead:

class Box[-A]
//       ^^^
 
 def foo[A](xs: List[Box[A]]) = xs
 
 def boxes: List[Box[?]] = ???
 
 val boxes2 = foo(boxes)

Note the contravariant annotation on A. Have to say, that’s probably not what you want. You can’t pull anything out of the box that way.

BTW, what is a List[Box[?]] anyway?

Imagine this:

def baz[A](ehs: List[A]) = ehs

def ehs: List[?] = ???

val ehs2 = baz(ehs)

A List[?] is a list of elements that all share the same indeterminate supertype at runtime, but I don’t know what that supertype is from one list instance to another. Never mind, wildcard capture gives that supertype a name, A in baz, because it’s got one list instance to play with, namely ehs.

A List[Box[?]] is a … hmm … I think a list of boxes whose supertype is completely indeterminate, and may fluctuate from one box to another in the same list. Inside of foo, I can’t name those supertypes with just one type A.

This is a bit of a woolly argument, because I’m using a picture based on a collection layout motivated by List, but if you take any arbitrary F[+X] and think of how to get at the Xs via F’s interface, I think you’ll end up with the same conclusion.

Having the contravariant definition of Box circumvents this argument, because there is no way of pulling out anything from a box in the list that would require being typed with A.

Here’s a more elaborate example:

case class Box[A](name: String, data: A)

def foo[A, B](xs: List[Box[A]], f: List[Box[A]] => B) = f(xs)

def strBoxes: List[Box[String]] = ???

def unknownBoxes: List[Box[?]] = ???

val namesOfStrBoxes = foo(strBoxes, _.map(_.name))

val computeDataOfStrBoxes = foo(strBoxes, _.map(_.data).mkString)  

val namesOfUnknownBoxes = foo(unknownBoxes, _.map(_.name))

It would seem to me that we should be able to take the names of the unknown boxes, even if we don’t know what the type of the data is.

Here the error on the last line is slightly different though:

Found:    List[Box[?]]
Required: List[Box[Any]]

(It is true that making Box contravariant solves it, but suppose I don’t control the definition of Box.)

If you want to get the names, why not just work with List[Box[?]] and have done with it, calling .map directly on the list in its original form?

Your initial problem seems to be moving around in response to the answer. Rather than play hunt the slipper, it seems easier to ask “What problem are you really trying to solve?”.

I’m bowing out of this for a while, but others may wish to take this up in the meantime….

List[Box[?]] means that every element of the list can have a different type. So we can’t assign it to a List[Box[A]] since that implies all elements have the same type. For instance, this typechecks:

val xs: List[Box[?]] = List(Box[Int](1), Box[String]("a"))
1 Like

This makes sense, but I still don’t fully get it.

I don’t understand why this compiles:

class Box[A]

def test[A](x: Box[A]) = x

def box: Box[?] = ???

def a = test(box)

While this does not:

def test2[A](x: Box[Box[A]]) = x

def boxbox: Box[Box[?]] = ???

def b = test2(boxbox)

Box[?] is an existential that extends one constructor outwards. Box[?] is
Box[A] for some A. Box[Box[A]] is Box[Box[A] for some A], not
Box[Box[A]] for some A. Existentials like this are easy to get wrong,.

1 Like

In other words, you don’t know whether the cat is alive until you open the box.

1 Like

Don’t worry about that moggy, it’s not dead - just taking it easy.

It’s on a furlough.

1 Like