On Mon, May 9, 2022 at 2:14 PM Brian Goetz <brian.go...@oracle.com> wrote:
Now, let's step out onto some controversial territory: how do we spell .ref > and .val? Specifically, how good a fit is `!` and `?` (henceforth, > emotional types), now that the B3 election is _solely_ about the > existence of a zero-default .val? (Before, it was a poor fit, but now it > might be credible. Yet another reason I wanted to tease apart what > "primitive" meant into independent axes.) > I'm certainly open to the possibility that `?` or `!` can help us out here. But the only way it can fly is if it is clearly a stepping stone toward a proper nullable-types feature. We would not want to get stuck here. That unfortunately forces us to have some clear idea how we would want/expect such a feature to look and work. My goal here is not to dive into the details of "let's design nullable > types", as that would be a distraction at this point, > (out of order reply) Well... I'm sorry for what follows, then. I think there is no way to know whether current proposals would be painting ourselves into a corner unless we explore the topic a bit. Here is my current concept of this beast: * bare `String` means what it always has ("String of ambiguous nullness") * `String!` indicates "an actual string" (I don't like to say "a non-null string" because *null is not a string!*) * `String?` indicates "a string or null". * `!` and `?` also work to project a type variable in either direction. * Exclamation fatigue would be very real, so assume there is some way to make `!` the default for some scope * javac (possibly behind a flag) would need to treat `?` and a suitably-blessed `@Nullable` identically, and same for `!` and `@NonNull`; there is just no way to survive a transition otherwise Enter Valhalla: * (Let's say we have B1, B2a/B3a (atomic), and B2b/B3b ("b"reakable?)) * On a B3 value type like `int`, `?` would be nonsense and `!` redundant. * That's equally true of a B3 value type spelled `Complex.val` (if such a thing exists). * (assuming `Complex` is ref-default) all three of `Complex`, `Complex?`, and `Complex!` have valid and distinct meanings. Now, imagining that we reached this point... would B3a/B3b (as a language-level thing) become immediately vestigial?. With Complex as a B2a or B2b, would `Complex!` ever not optimize to the B3-like *implementation*? I think the (standard) primitives could be understood as B2 themselves, with `int` just being an alias for `Integer!`. Obviously, if it would become vestigial, then we should try to avoid ever having it all, by simply :-) delaying it and solving B2-then-nullness. Pro: users think they really want emotional types. > Quibble: nah, we *know* we want them... > Con: These will surely not initially be the full emotional types users > think they want, and so may well be met by "you idiots, these are not the > emotional types we want" > We don't have to worry about this if we have a good story that it's a stepping stone. The stepping stone could be that it just doesn't work for B1 types yet. I would say that there's a moral hazard that people might choose B2 just to get that... but since that only happens if they don't *need* identity... we'd like them to do that anyway! > Con: To the extent full emotional types do not align clearly with > primitive type projections, we might be painted into a corner and it might > be harder to do emotional types. > I'm questioning whether we would need primitive type projections at all, just nullable/non-null type projections. > Risk: the language treatment of emotional types is one thing, but the real > cost in introducing them into the language is annotating the libraries. > Having them in the language but not annotating the libraries on a timely > basis may well be a step backwards. > For a while we'd only have to annotate as we migrate B1 -> B2. And it can be automated to a significant degree, more than halfway I think. If we had full emotional types, some would have their non-nullity erased > (`String!` erases to the same type descriptor as ordinary `String`) and > some would have it reified (Integer! translates to a separate type, the `I` > carrier.) This means that migrating `String` to `String` might be > binary-compatible, but `Integer` to `Integer!` would not be. (This is > probably an acceptable asymmetry.) > Agree acceptable. > But a bigger question is whether an erased `String!` should be backed up > by a synthetic null check at the boundary between checked and unchecked > code, such as method entry points (just as unpacking a T from a generic is > backed up by a synthetic cast at the boundary between generic and explicit > code.) This is reasonable (and cheap enough), but may be on a collision > course with some interpretations of `String!`. > There seem to be a continuum of approaches from "more checking/less pollution" to "more pollution/problems get found far from where they really happened." The generics experience was that few people bothered to use `checkedCollection()`, and I doubt many added type checks via bytecode either, and it all worked well enough, buuut there are a few reasons for that that don't translate to null. -- Kevin Bourrillion | Java Librarian | Google, Inc. | kev...@google.com