Draft JEP on Primitive types in patterns, instanceof, and switch
Brian Goetz
brian.goetz at oracle.com
Fri Jan 27 02:39:52 UTC 2023
On 1/26/2023 5:01 PM, Stephen Colebourne wrote:
> "On Thu, 26 Jan 2023 at 09:49, Angelos Bimpoudis
> <angelos.bimpoudis at oracle.com> wrote:
>> I would like to share this draft JEP with you about primitive types in patterns, instanceof, and switch:
>>
>> https://openjdk.org/jeps/8288476
>>
>> "Enhance pattern matching by allowing primitive types to appear anywhere in patterns. Extend instanceof to support primitive types, and extend switch to allow primitive constants as case labels."
>>
>> Comments very much welcomed!
> I am implacably opposed to this JEP.
Yes, you've aired these concerns several times before, both publicly and
privately, and I figured you probably were not going to change your
mind. Unfortunately, while I understand where your objections come
from, after reading them carefully many times, in the end, I still don't
find them persuasive. If it makes you feel better, your arguments (and
those of others) did help us come to a better understanding of what we
want to do here, but unfortunately it couldn't change the fact that you
have a different idea of how the language should be evolved than we do.
In a community as large as Java, it is inevitable that this occasionally
happens.
You are looking at this feature bottom-up, as in "would I want to write
code to do this", and as a user of the language, that makes sense --
you're trying to figure out what new programs it lets you write, and
evaluating how interested you are in writing those programs. But our
motivation for this JEP is top-down; it is about driving the language to
a more consistent place, where aggregation and decomposition not are
both compositional, but compose in precisely the same way (only in
reverse), so we can take apart the composition of aggregations with the
composition of similar patterns -- without having to reason about
arbitrary distinctions like "unless it involved a primitive widening."
I've covered some of the specifics of this goal in greater detail in
numerous previous mails, so I don't want to repeat them here. Here's one
example:
- https://mail.openjdk.org/pipermail/amber-dev/2022-November/007601.html
> I think it breaks a fundamental
> tenet of the Java language
I realize you feel unheard because you haven't been able to convince us
to your point of view, but escalating to hyperbole ("fundamental tenet",
"red line", "shatter") or insulting rhetoric ("utterly nonsensical"),
doesn't help your case. I won't be responding to these parts.
> A large part of the Goals and Motivation section are focussed on the
> question of how to check and convert from `int` to `byte` or similar.
I actually agree with this point -- that the goals and motivation don't
tell the whole story. This is an early draft of the JEP, and the goals
and motivations need to be tightened up to capture the bigger picture.
We'll be evolving the goals and motivation as we go. Comments are
welcome on how to improve the JEP draft, but we're past the "is this a
good idea" stage.
> This isn't a problem people are crying out to be fixed.
As has been explained before, not all language changes need to be
reactions to "things people already perceive as pain points". You've
already several times offered feedback that rounds to "I wish pattern
matching were a smaller feature". But there is a big agenda for pattern
matching here, and we're looking ahead to where we're trying to get to,
not just the next meter of pavement. People have not yet started to use
sophisticated nested patterns, so they haven't experienced this pain yet
-- but they will, if we don't head it off before they get there.
> There is simply no comparison here. Dog and Animal are subtypes of
> Pet, a concept that has been baked into the language since day one,
> and is fully understood by all. By contrast, there is absolutely no
> subtyping relationship between int, byte and short, and again this
> fact has been baked into the language since day one.
You've floated this theory before -- that instanceof "is" a subtype
check, "full stop". And I can't fault you for choosing this particular
mental model -- it's a useful one, or at least it has been so far. But
it's quite a leap to claim that somehow this is the only way it would
ever be reasonable to think about it, and peppering your claims with
more absolutes doesn't make it so.
In reality, `instanceof` is closely related to _casting_. In
pre-patterns code, nearly 100% of instanceof checks are followed by a
cast to the same type (why else would you ask instanceof?), and almost
as many casts are preceded by an instanceof for that same type (and many
that don't are bugs). This is not an accident. Instanceof is how we ask
whether it would it be safe to cast to a given type, before doing
something that might fail. It has been restricted to reference types
for reasons that were sensible in 1995, started to fray when we added
autoboxing, are fraying further now that we have nested patterns, and
will further unravel when we get to Valhalla. But if you read JLS Ch5
enough times and listen carefully, *it already tells you* what
`instanceof` means when applied to primitives! It means the same thing
as for references -- "would it be safe to cast to this type". The set
of things that can go wrong when casting to/from primitives are slightly
different -- you can lose precision silently instead of throwing CCE
loudly -- but it is the same question, and the answer is sitting there
in JLS Ch5 already. (This is covered in greater detail in the linked-to
message above.)
A telling thing is that if you look at the spec of this work, what
you'll see is that it is almost entirely _removing_ existing
restrictions; the main thing that is added is a definition of when a
cast is "lossy" and when it is "exact" (and these are exactly what you'd
expect them to be.) The rest flows entirely from the existing
definition of cast conversion and that `instanceof` is the precondition
for casting being safe. There's almost nothing new here, just exposing
what is already there to a broader domain.
I get that this seems uncomfortable to some; if you cannot get past the
belief that instanceof could only ever be about subtyping between
reference types, you're going to tie yourself in knots. But "instanceof
is about subtyping" is merely one convenient way to explain the current
behavior. But its kinship with casting is deeper than that.
> The root cause of the issue here is trying to treat Object hierarchy
> conversion and conversion between different primitive types as being
> somehow equivalent. They are not. Java does not have a mechanism that
> allows a LocalDate to be assigned to a String, even though there is a
> perfectly reasonable way to do so. Instead, you have to explicitly
> perform the conversion by calling toString(). That is because
> `LocalDate` and `String` are separate types with no subtype hierarchy.
I'm glad you bring up "assignment". Assignment is covered in JLS 5.2,
"Assignment context". And note that the word "subtyping" does not
appear in JLS 5.2! What it says is that in an assignment context, the
conversions that are allowed include widening primitive conversion (int
to long), widening reference conversion (String to Object), boxing
conversion (int to Integer), and certain others. The language used by
the spec here doesn't treat subtyping as something special; it even uses
the same word to describe "int to long" as it does for "String to
Object" -- they are both widening conversions.
Look, I get it. Individual developers are free to come up with their
own mental models for how the language works, and mental models can be
immensely useful. But we shouldn't confuse our mental models with
Universal Truth, and sometimes reality takes a turn we didn't expect,
and it can be momentarily disorienting.
> In my view, the only pattern matching checks that make sense here are
> those in line with the separation between types and values of
> instances.
That is one view, and I'm not going to say it is intrinsically bad. But
there are more things in heaven and earth. Our goal is to evolve the
language consistently both with where it has come from, and with a view
towards where we are going -- and our goals for pattern matching are
deeper than merely fusing type tests and casts. As a result, we may
push things farther than you would, and that's OK. But the arguments you
make, while passionate, are basically circular; you've assumed a "how it
is" that can lead only to "how you want it to be."
I realize you won't be convinced, and there may be others in the same
boat -- and that's fine. If you have *new* arguments that can be
expressed constructively, they are welcome, but rehashing the same
arguments another time is unlikely to be helpful.
In ten years, if you still disagree that this wasn't the right move,
I'll buy the beers. But I'm pretty sure I won't have to; I think this
will make a lot more sense in the rear-view mirror than it feels right
now looking down the road.
More information about the amber-dev
mailing list