Migrating methods in Collections

Brian Goetz brian.goetz at oracle.com
Mon Dec 21 18:03:42 UTC 2015

> Instead of demoting, say, remove(int) to a partial method, simply
> hide it from all source level 10 code, which will only be able to access
> removeAt, even on a List<String> (the method will still be in the class,
> of course). Cons: breaks source compatibility (but not binary
> compatibility) in a more major way than ever before, but Java has
> mechanisms to deal with that (source level), and automatic migration
> tools should be easy. Pros: less strange than partial methods; simpler
> to implement; a more general (albeit crude) migration mechanism, or,
> rather binary-compatible source-deprecation mechanism.

I think people would be pretty ticked off if Map.get() just went away. 
I think the response would be: "Those idiots decided to change their 
libraries for their own reasons, I have no intention of ever 
specializing my Map, and yet I have to change my code anyway."

Secondarily, while we might plan to do this to Collections in version N, 
other generic libraries (including other JDK libraries) might wait until 
N+3 to anyfy their own.  Realistically this means we'd be forced to 
expose whatever versioning mechanism we use here for general use -- 
which seems at least as potentially confusing (and open to misuse) as 
partial methods.  While a method-grained versioning mechanism seems like 
it might solve a lot of problems (for example, we wouldn't have needed 
to do default methods), so far, we've not seen any satisfactory theory 
that we'd want to consider building on -- there have been many attempts 
in the academic literature but I think method versioning in object 
oriented systems is still an unsolved problem.  So I'm wary this could 
degenerate into something far worse than partial methods -- a bad 
versioning system.

Separately, I think the distaste for partial methods may also be a 
little bit an allergic reaction to the deliberately-bad syntax we're 
using.  I'll share a caricature of a past interaction on this topic 
(with someone on this list, actually) that illustrates the power of 
implicit syntactic biases:

Him: This where-clause thing is totally confusing and will be completely 
foreign to Java developers!  Augh!

Me: What if I wrote it like this instead:

     boolean remove(Collection<ref T> this,
                    int index)

Is that less confusing?  (oh, and BTW this builds on the *existing Java 
8 syntax* that is already there for explicit receiver parameters, which 
we added so they can be annotated.)

Him: That's so much better!  Then its clear that the restriction is just 
part of the method signature.  And if there is more than one partial 
method called foo(), its clear from this that they are distinct overloads.

Now, I don't want to devolve into premature syntax bikeshedding, but my 
point is: I don't think the it is the concept that is fundamentally 
confusing, its just that we will (in addition to convincing ourselves 
that the model is sound, which is the task currently in front of us) 
then additionally have to fit it into a syntactic expression that makes 
sense to Java users.  (Coming up with a good syntactic form is also 
hard, so I want to first ensure we have a sound theoretical model before 
taking on unnecessary additional work.)

> Now, it is a dramatic break, but Valhalla is quite dramatic anyway.
> Partial methods are a migration measure (we wouldn’t have needed them
> had the APIs been designed with values in mind, right?) but they’ll stay
> a part of the language forever, and they don’t have the general
> usefulness of default methods (unless there are non-migration reasons to
> make use of partial methods that make sense in Java).

Mostly, but not entirely.  Partial methods also allow you to do this:

interface List<any T> {
     <where T=int> default long sum() { ... }

which is not strictly related to migration.  (Personally, I don't love 
this as a feature, because it's weaker than it first appears (think: 
"The Expression Problem"), and when you try to shore up these weaknesses 
with a more powerful slicing mechanism like <where T extends Numeric> it 
starts to get more complex -- but this form of partial method is also 
part of the current best approach we've got for being able to replace 
IntStream with Stream<int>, which is easier in some ways, and harder in 
others, than Collections.)

However (picking up the above syntactic form), would you find these 
signatures terribly confusing?

interface Stream<any T> {
     int    sum(Stream<int>    this);
     long   sum(Stream<long>   this);
     double sum(Stream<double> this);

More information about the valhalla-spec-experts mailing list