Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the significant of the placement of the "forSome" clause in Scala generics syntax?

Based on the answers to this question, it appears that placing "forSome" after a component of the type definition is different from placing it at the end of the whole thing. For instance, it seems there is a difference between the following:

def one: Foo[U >: T] forSome {type U >: T}

def one: Foo[U forSome {type U >: T}]

The Scala language specification does not seem to say anything about the difference, and I would have imagined that moving the quantifiers to the outside would make no difference. If it did make a difference, I would have thought it would be as described in this answer, which basically says Set[X forSome {type X}] allows X to vary between set elements, where Set[X] forSome {type X} does not. However, this does not seem to be the whole story and/or is not correct, because this does not compile:

trait Bar {
   def test: Set[X] forSome {type X}
}

def test(b: Bar) {
  val set = b.test
  val h = set.head
  set.contains(h)
}

But this does:

trait Bar {
   def test: Set[X forSome {type X}]
}

def test(b: Bar) {
  val set = b.test
  val h = set.head
  set.contains(h)
}

It seems as if Set[X] forSome {type X} creates a separate abstract type for every usage site in the instantiated class, where Set[X forSome {type X}] creates only one and uses it for the entire class. This is the opposite of what I would have expected and seems inconsistent with the answer reference above.

like image 231
Simon K Avatar asked Oct 01 '22 21:10

Simon K


1 Answers

A couple of observations to start with: X forSome { type X } is just a fancy way of writing Any—it's a type we don't know anything about, so it must be at the top of the type hierarchy. If you don't believe me, ask the compiler:

scala> implicitly[Any =:= X forSome { type X }]
res0: =:=[Any, _] = <function1>

Yep, it agrees.

Relatedly, the following won't compile:

scala> val xs: Set[X forSome { type X }] = Set[Int](1, 2, 3)
<console>:7: error: type mismatch;
 found   : scala.collection.immutable.Set[Int]
 required: Set[X forSome { type X }]
Note: Int <: X forSome { type X }, but trait Set is invariant in type A.

Which isn't surprising given what we just learned. Set is invariant in its type parameter, so a Set[Int] isn't a Set[X forSome { type X }] (i.e., a Set[Any]).

Given all of this it's also not too surprising that the second test method compiles. When we take the head of b.test, we get an X forSome { type X } (i.e., an Any), and we need an X forSome { type X } (i.e., an Any) for b.test.contains.

So now for the first Bar. Consider the following:

scala> val xs: Set[X] forSome { type X } = Set[Int](1, 2, 3)
xs: Set[_] = Set(1, 2, 3)

Here we've said xs is a set of some specific type X, but we're immediately going to forget everything about X. Note that unlike the xs definition above, this does compile, since we're not saying that xs is a set of anything, just that it's a set of some specific type we don't know (or rather that the compiler doesn't know).

This means that there's absolutely no possible a that will make xs.contains(a) compile. Let's try an obvious one:

scala> xs.contains(1)
<console>:9: error: type mismatch;
 found   : Int(1)
 required: X
              xs.contains(1)
                          ^

The error message here is interesting—we know that X is actually Int, but the compiler doesn't, since we explicitly asked it to forget that fact with the forSome { type X }. You can see a similarly interesting message by rewriting your test method for the first Bar as follows:

def test(b: Bar) = b.test.contains(b.test.head)

This also won't compile, with the following message:

found   : (some other)X(in method test) where type (some other)X(in method test)
required: X(in method test) where type X(in method test)
      def test(b: Bar) = b.test.contains(b.test.head)
                                             ^

That is, even though we just pulled that b.test.head out of b.test, we still can't apply b.test.contains to it. We've told the compiler that the only thing it knows about the item type of b.test is that it exists, so it's not going to keep track of the fact that b.test.head is the kind of thing that we should be able to apply b.test.contains to.

like image 184
Travis Brown Avatar answered Oct 04 '22 21:10

Travis Brown