Primitive-parametrized classes covariance with pre-existing specialized types
Paul Bjorkstrand
paul.bjorkstrand at gmail.com
Thu May 16 13:52:19 UTC 2024
It would compile if Predicate<T> extends Function<T, boolean>. I don't
think that specialized generics would equate to "immediately remove all
hand specializations." With Predicate's wide use, it would probably take a
decade or longer to fully remove it from the ecosystem, and that is
assuming it would be removed at all (how much code still uses HashTable,
when there are now generic replacements?)
// Paul
On Thu, May 16, 2024 at 8:23 AM Olexandr Rotan <rotanolexandr842 at gmail.com>
wrote:
> While this is in some sense natural, this sort of “anything goes”
>> flexibility that incorporates both nominal and structural subtyping creates
>> new problems, and even if not, is unlikely to move us very far in the
>> direction of “IntStream is-a Stream<int>”.
>
> My main concern with transitioning from Predicate<T> to Function<T,
> boolean> (or any other primitive-specialized, predicate is not the biggest
> problem, obviously) is that if currently, hypothetically, we have changed
> all specialized functional types to generic functional types, even if they
> essentially "behave the same", would completely brake
> backwards compatibility. Not even talking about binary compatibility, about
> which I don't know virtually anything, I could easily think of a scenario
> where this change would break source compatibility:
>
> ToIntFunction<T> mapper = ....;
> someList.stream().mapToInt(mapper) .... // now won't compile if mapToInt
> expects Function<T, int>
>
> That was the main concern that I have tried to express with the
> initial letter. I am all for any easier workaround than the one I have
> described, but if we want to move to “IntStream is-a Stream<int>” (and I
> guess we really want), some way to convert between specialized and generic
> types should be present at least in some form.
>
> On Thu, May 16, 2024 at 3:35 PM Brian Goetz <brian.goetz at oracle.com>
> wrote:
>
>> I am currently working on some Stream API enhancements and its internals
>> have become really complicated as time passed, mainly (imo), due to
>> primitive-specialized pipelines.
>>
>>
>> Slight correction: the internals were complicated from day 1, for this
>> reason. This is not something that “happened over time”; the
>> hand-specialization was part of its fundamental complexity.
>>
>> JEP 402, at first glance, looked like salvation
>>
>>
>> You are mistaking JEP 402 for the full sweep of the Valhalla project.
>> Indeed, Valhalla grew out of the observations of the painfulness of hand
>> specialization (among other challenges). But before we can get to full
>> generic specialization with both layout and code specialization, we have to
>> cover some fundamentals first. There are many steps along the way here.
>> We walk before we can run.
>>
>> What you are asking for is to blur the distinction between nominal and
>> structural function types, allowing Predicate<T> and Function<T, boolean>
>> to be considered “the same” in some way. While this is in some sense
>> natural, this sort of “anything goes” flexibility that incorporates both
>> nominal and structural subtyping creates new problems, and even if not, is
>> unlikely to move us very far in the direction of “IntStream is-a
>> Stream<int>”.
>>
>> Rest assured that we share your goal, but the path to get there is
>> somewhat longer.
>>
>> , but the more I think about it, the more problems I see with integrating
>> legacy with new features. Specialized stream pipelines are wired with
>> specialized functional interfaces (ToXFunction, XPredicate etc.). This (and
>> also some specialized for primitive ops of streams), make it impossible to
>> harmlessly switch from XStream to Stream<X> (where X is primitive).
>>
>> While the second issue (specialized methods) could be addressed
>> separately, one thing that could certainly help with eventually moving on
>> from primitive-specialized types induced mess in current APIs is
>> introducing covariance between generic types and their specialized
>> versions. It is intuitive that ToLongFunction<T> is essentially the same as
>> Function<T, long>. I saw a note at the end of the JEP that Long! and long
>> are not fully interchangeable for some internal for JVM reasons, but the
>> API that these interfaces provide are exactly the same. I understand that
>> if public APIs that types provide are similar, that still doesn't mean
>> their behaviour is the same, but while it does not mean it, it could be the
>> case (and is a case in huge amounts of situations as when it comes to
>> specialized types).
>>
>> If such covariance would be present in language, huge amounts of code
>> could be just clamped as they are no longer needed. For example, whole
>> IntPipeline could be clamped to IntPipeline extends ReferencePipeline<int>
>> { /* proprietary methods of IntStream */ } instead of virtually a second
>> ReferencePipeline class but specialized for int. This example is just the
>> thing that is closest to me right now, but I am sure anyone could think of
>> a way where this could be helpful for them.
>>
>> I am not really sure how this is implementable in a way that does not
>> weaken the type system though. We can't just let users write that class A
>> is covariant to class B<X> or something like that for obvious reasons. The
>> easiest solution is to just add some compiler rules for a pre-defined
>> number of types in jdk, but this still will leave all library devs and just
>> users that had to write specialized types for some reason stranding. The
>> rules for this covariance could surely be a topic of discussion for more
>> experienced developers than me, but to have this feature would be a
>> dramatic step forward in Java.
>>
>> Regards
>>
>>
>>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/valhalla-dev/attachments/20240516/4c45260e/attachment.htm>
More information about the valhalla-dev
mailing list