Someone is confused by pattern matching (was: Pattern matching on primitive types mxied with instanceof is a footgun)
Brian Goetz
brian.goetz at oracle.com
Wed Feb 5 06:55:52 UTC 2025
Let me address Tagir’s points:
> On Feb 4, 2025, at 5:00 PM, Tagir Valeev <amaembo at gmail.com> wrote:
>
> Well, I would say that Remi has a point. This is a silent mistake that can be made.
Agree, the construct can be used incorrectly; in this case the user thought something different from what was going to happen. It is not clear what they thought was going to happen, though; perhaps they mistakenly thought that the coordinates were ints, or perhaps they mistakenly thought that the floats would be lossily converted to ints, as would happen with an assignment or blind cast. If we don’t know what mistake they were making, it’s hard to correct for that (and even harder to correct for both.)
> You don't see the declaration, as it's in another file so you may forget about original types and assume that they are ints.
Yes, we discussed this a lot when we did record patterns. But I must point out here: this has absolutely zero to do with primitive patterns. It just so happened that in this example, primitives were used, but one could make the same mistake with a Box<Object> and a nested `String` pattern.
> The code compiles and doesn't even throw an exception, it just works incorrectly.
Here is where I disagree! It does work correctly! It asks whether the coordinates can be safely converted to integers, and only proceeds if that is the case. And that is a totally reasonable and useful question to ask! It is just that this is not what the user *thought* was going to happen (and, see above, we’re not even sure which mistake the user made). But what the user thought here is only one reasonable way to interpret what should happen.
You could argue that this syntax is too “terse”, and this is the source of the problem; we could have specified nesting to require a claim of totality or partiality:
case Box(partial String s):
case Box(total Object o):
But I don’t think anyone would have thanked us for that explicitness. (And if the markers were optional, the mistake could still happen.)
> It would be justified if such code pattern (converting from double to int only when double fits the int) was common, so conditional narrowing could be useful in many other places. But the fact is that it's highly uncommon. Nobody does this. Many other pattern matching features are nice. E.g., I often see the code which can be migrated to pattern switch (we are still on Java 17) and I really want to use it. However, I never see the code which can be migrated to a narrowing primitive pattern.
>
> I don't agree that we should drop primitive patterns completely, but we may reconsider floating point <-> integral conversions and disable them in patterns for a while (enabling them back later will be always possible).
Both of these arguments are dangerous arguments; they argue to introduce arbitrary asymmetries in how it works, just to match our guesses about “what users think.” In the first case, you’re saying that we should forever treat primitives differently from objects (because they work differently today and we have gotten used to it?); in the second, that instanceof should diverge from casting. Being able to interpret a pattern match by asking “what would casting do” is a very powerful technique. Undermining that to avoid a little bit of discomfort would be a huge mistake.
I get how these feel like we are “helping” users by protecting them from foreseeable mistakes. But they are also not helping, by making the language more complicated, arbitrary, and asymmetric (and less expressive). Both paths offer the chance for confusion, but if we choose arbitrary complexity, the confusion is endemic and permanent, whereas if we choose simplicity and uniformity, the confusion eventually recedes into the past.
More information about the amber-spec-experts
mailing list