Re: Reviewing feedback on patterns in switch
On 2022-02-16T10:49:19 -0500 Brian Goetz wrote: > One thing that we have, perhaps surprisingly, *not* gotten feedback on > is forcing all non-legacy switches (legacy type, legacy labels, > statement only) to be exhaustive. I would have thought people would > complain about pattern switches needing to be exhaustive, but no one > has! So either no one has tried it, or we got away with it... > I tried it and liked it. It didn't go unnoticed. :) -- Mark Raynsford | https://www.io7m.com
Re: Reviewing feedback on patterns in switch
On 2022-01-25T19:46:09 + Brian Goetz wrote: > We’ve previewed patterns in switch for two rounds, and have received some > feedback. Overall, things work quite well, but there were a few items which > received some nontrivial feedback, and I’m prepared to suggest some changes > based on them. I’ll summarize them here and create a new thread for each > with a more detailed description. > > I’ll make a call for additional items a little later; for now, let’s focus on > these items before adding new things (or reopening old ones.) > > 1. Treatment of total patterns in switch / instanceof > > 2. Positioning of guards > > 3. Type refinements for GADTs > > 4. Diamond for type patterns (and record patterns) Hello! I'm a little late to the party, as ever, but is there a specific build I should be looking at so that I can get a better idea of what the current state of things are? -- Mark Raynsford | https://www.io7m.com
Sealed interfaces with hidden implementation superclasses
(Apologies for the possible double post: I tried to post this to amber-dev@ and the message apparently never made it there. I asked amber-dev-owner@ but got no response there either). I've run across some behaviour I didn't anticipate with regards to sealed interfaces. I suspect that it's expected behaviour, but I feel like maybe the type system is overly constraining in this particular case. Imagine a small UI library with a sealed set of primitive components: sealed interface Component permits Button, TextView, ImageView {} non-sealed interface Button extends Component {} non-sealed interface TextView extends Component {} non-sealed interface ImageView extends Component {} The set of primitive components is sealed because doing so simplifies other parts of the system; more complex components are just aggregates of the primitives. Then, in one or more separate modules, we have some platform-specific implementation classes: final class X11Button implements Button final class X11TextView implements TextView final class X11ImageView implements ImageView final class QNXButton implements Button final class QNXTextView implements TextView final class QNXImageView implements ImageView This is all fine so far. The problem occurs when one of those implementations wants to introduce a private abstract superclass that contains shared implementation code but that - importantly - isn't actually intended to ever be observed on its own outside of the module. Naturally, that superclass wants to implement Component so that it can provide implementations of the common methods: abstract class QNXComponent implements Component final class QNXButton extends QNXComponent implements Button final class QNXTextView extends QNXComponent implements TextView final class QNXImageView extends QNXComponent implements ImageView The compiler (rightfully) complains that QNXComponent isn't in the "permits" list of Component. We can obviously remove the "implements Component" from QNXComponent, but this then means adding boilerplate @Override methods in each of QNXButton, QNXTextView, etc, that call the now-non-@Override methods in the abstract QNXComponent class. -- Mark Raynsford | https://www.io7m.com
Re: New pattern matching doc
On 2021-01-15T12:02:41 -0500 Brian Goetz wrote: > Maurizio had a clever idea for how to rescue this issue. > > As in (not a serious syntax proposal): > > try-match (x : big-honking-pattern) { > // happy-path code > } > catch (MatchFailException e) { // checked exception > // exception captures which sub-pattern failed > } > Hah, synchronicity! Yes, I was drafting an email with a suggestion for something similar, although i used a __MATCH_OR_DIE expression form that looked like a cast, and an unchecked exception. Did I just imagine it, or was there a suggestion for something like this in the past?: var Point(var x, var y) = ... That would have a similar aspect of assertion (as it does in Haskell, ML, etc) and would obviously need to have the same "match or die" semantics. I think something akin to try-match is perfectly reasonable. -- Mark Raynsford | https://www.io7m.com
Re: New pattern matching doc
On 2021-01-06T15:52:19 -0500 Brian Goetz wrote: > I have pushed > > https://github.com/openjdk/amber-docs/blob/master/site/design-notes/pattern-match-object-model.md > > to the amber-docs repo, which outlines the direction for how patterns > fit into the object model. I've been following along with the discussion on patterns and guards, and I have a concern that I didn't anticipate: The practicality of using patterns to _parse_. Mostly, I'm eyeing "A possible approach for parsing APIs" [0]. The issue, as I see it, is that I'm not entirely sure if a failure to match in such a large nested structure is going to help me construct a usable _error message_. As you're certainly aware, about 80% of the code in any good compiler is devoted to giving error messages that are actually useful to users. If I get a parse error, for example, I want to know - down to the level of lines and columns - which part of the input failed to match expectations. Is matching a structure like that going to be able to provide useful error messages if input _doesn't_ match? It seems like it just provides a binary true/false answer. If it's the case that it won't actually help with giving useful error messages, then I think that reduces the applicability of patterns to this particular class of problems. It follows that it also might mean that the nice things we're putting on top (such as the composition of patterns) won't actually see practical use, because people end up writing very simple patterns with at most one level of nesting. Now, you know me, I'm the first to try to apply pattern matching and algebraic data types to any and every problem. I'm a little concerned about possible over-engineering though. [0]https://github.com/openjdk/amber-docs/blob/master/site/design-notes/pattern-match-object-model.md#a-possible-approach-for-parsing-apis -- Mark Raynsford | https://www.io7m.com
Re: Unexpected compilation error with generic sealed interface
On 2020-12-28T11:41:36 -0500 Vicente Romero wrote: > Hi, > > I believe this to be the same issue already addressed in [1], which has > already been fixed in 16 and should be forward-ported to 17, Looks like it, yes! Didn't realize the same code had been submitted there. To be clear: The original symptom was an actual compiler crash. Now we just get a compilation error ("I cannot be converted to C"). An error is obviously better than a crash, but I don't think this code should result in an error at all. -- Mark Raynsford | https://www.io7m.com
Re: Unexpected compilation error with generic sealed interface
On 2020-12-27T23:40:50 +0100 Remi Forax wrote: > Hi Mark, > this is an interesting snippet, while i struggle to understand why someone > want to write a code like that, I is parametrized by T but the only possible > subclass implements I so T is useless here, > anyway, the compiler should not reject that code so it's a bug. Hello! I think the original code was a collection of little functional data structures (Option, List, etc) to try out the combination of records and sealed classes. The code above is the result of removing things one by one until the result was a minimal example that would trigger the error. -- Mark Raynsford | https://www.io7m.com
Unexpected compilation error with generic sealed interface
Hello! A friend of mine handed me this example that fails to compile using JDK 17 EA 3: ~~ final class SealedExample { private SealedExample() { } // Compiles if you remove `sealed` or if `I` is not generic. sealed interface I { final class C implements I { } } static void f(final I x) { if (x instanceof I.C) { } } } ~~ The error is: ~~ src/main/java/SealedRecord.java:14: error: incompatible types: I cannot be converted to C if (x instanceof I.C) { ^ 1 error ~~ The error goes away if you remove the word "sealed" from the interface. In her words: "It breaks simple things like Option.". I can see the reasoning required on behalf of the compiler: You've handed me an I, and by the definition of I, there's exactly one class that could yield an I: C. I'm unsure of whether the compiler should be rejecting these definitions or not. -- Mark Raynsford | https://www.io7m.com
Re: Next up for patterns: type patterns in switch
On 2020-07-23T12:20:01 -0400 Brian Goetz wrote: > In case I wasn't clear, this was a proposal to proceed on _right now_. > There's been no comments so far; should I take that as "perfect, ship it?" Apologies for my lack of participation in these lists lately. I've been inundated with non-JDK work over the past six months or so. That said, I've read this proposal multiple times and I can see nothing to which to object. Best to interpret my silence as agreement. :) -- Mark Raynsford | https://www.io7m.com
Re: Sealed types
On 2018-11-30T11:16:51 -0500 Brian Goetz wrote: > > Yes. As mentioned before, I expect unsealing a _class_ to be more > common than unsealing an _interface_, because classes have more tools > with which to defend against rogue subtypes. > Sounds good, thanks! Do you have any documentation on how Scala's opt-in (not transitive) sealing became a source of bugs? This is news to me, I actually would have expected the opposite. -- Mark Raynsford | http://www.io7m.com
Re: Sealed types
On 2018-11-27T17:20:54 -0500 Brian Goetz wrote: > Since we’re already discussing one of the consequences of sealed types, > let’s put the whole story on the table. These are my current thoughts, > but there’s room in the design space to wander a bit. Is the intention to allow this: module M; package x.y.z; __Sealed interface I { } class A implements I {} __Unsealed interface B extends I {} Then, in another compilation unit: module N; package a.b.c; class C implements B {} ... and then: module O; package d.e.f; I x = new C(); switch (x) { case A: ... case B: ... } The switch should be considered exhaustive, because both A and B are direct children of the sealed interface I. However, it's possible for me to add extra subtypes to B because B isn't sealed, and I still get exhaustiveness without mentioning C explicitly. I would expect the following *not* to be exhaustive: module O; package d.e.f; I x = new C(); switch (x) { case A: ... case C: ... } I've mentioned modules and packages explicitly above because it's not clear to me if explicitly unsealed interfaces are permitted to have implementations in different packages or modules without also losing exhaustiveness. -- Mark Raynsford | http://www.io7m.com
Re: Intersection types in patterns
On 2018-10-20T12:42:42 -0400 Brian Goetz wrote: > > Is there a situation where you could have reached that case without > > having a reference to p? If I've understood so far: > > > > SomeSuperTypeOfPoint q; > > > > switch (q) { > >case Point(var x, var y) p: ... // p == q and p : Point > > } > > Yes, you could have gotten there via > > switch (getAnObjectForMe()) { > case Point(var x, var y) p: > } That is a good point! > > So in the Point case there, you could do something with the fields > > x, y > > Careful, binding variables — they may be copied from fields, but we don’t > know that. Deconstruction patterns are really like methods that > conditionally return multiple values; but methods don’t have to read fields > to return a value. Right. > > How is the resulting declaration scoped? What if I do... > > > > SomeSuperTypeOfPoint q; > > > > switch (q) { > >case Point(var x, var y) p: ... // p == q and p : Point > >case OtherPoint(var x, var y) p: ... // p == q and p : OtherPoint > > } > > This is well handled by the flow-scoping rules (and one of the reasons we > chose such a scoping). Yes, that's what I suspected. > So the first p is in scope for the statements that follow the first > case, and similar for the second. If you fall through from the first > to the second, then there are competing overlapping definitions of > `p`, and you get an error. So scoping is not a reason not to do this > (you could have collisions on x and y too, which would be handled by > the same set of rules), but I think what you’re getting at here is “I > don’t see how its needed, you can always pull the switch target into a > variable, and cast it.” Right, I'm up to speed. I retract my con! -- Mark Raynsford | http://www.io7m.com
Re: Intersection types in patterns
On 2018-10-20T11:40:39 -0400 Brian Goetz wrote: > > One area where we have a question about whether to support an optional > binding variable is in deconstruction patterns: > > case Point(var x, var y) p: ... > > Sometimes it is desirable to bind the cast target as well as its > components (Scala and Haskell support this with the `var@pattern` > syntax.) Can anyone offer pros/cons here for supporting this based in > real-world experience? (Assume someone has already said "I could > imagine how that might be useful.") Is there a situation where you could have reached that case without having a reference to p? If I've understood so far: SomeSuperTypeOfPoint q; switch (q) { case Point(var x, var y) p: ... // p == q and p : Point } So in the Point case there, you could do something with the fields x, y (including possibly mutating them) but then pass the sharper-typed p alias to some other method that can only take Points and not SomeSuperTypeOfPoints? Is the type of q assumed to be Point in that case (via the shiny new intelligent typing rules)? How is the resulting declaration scoped? What if I do... SomeSuperTypeOfPoint q; switch (q) { case Point(var x, var y) p: ... // p == q and p : Point case OtherPoint(var x, var y) p: ... // p == q and p : OtherPoint } I'm not sure how this would interact with the current scoping/fallthrough rules. In Haskell, @ patterns are often needed because of the support for equational definitions that immediately match upon their arguments. For example: length :: forall a. [a] -> Integer length [] = 0 length (x : xs) = 1 + (length xs) In other words, there was never a binding in scope that referred to the original list, only the deconstructed parts. I can't think of any case in Java where that could happen. I guess what I'm saying is that the con would be an extra set of syntax rules for something that might not actually be useful in Java, as it doesn't have these sorts of equational definitions and AFAIK there should always be a binding in scope upon which the matching is being performed. -- Mark Raynsford | http://www.io7m.com
Re: Updated pattern match documents
On 2018-09-07T14:41:12 -0400 Brian Goetz wrote: > I've updated the documents regarding pattern matching, and uploaded them > here: > > http://cr.openjdk.java.net/~briangoetz/amber/pattern-match.html > http://cr.openjdk.java.net/~briangoetz/amber/pattern-semantics.html I need to give the semantics document another serious read (I don't care much about syntax, personally), but at first impression, I particularly like how nullability has been handled. Nullable patterns seem like a great way to support both null-hostile and null-tolerant programming. -- Mark Raynsford | http://www.io7m.com
Re: Compile-time type hierarchy information in pattern switch
On 2018-04-03T12:36:43 -0400 Brian Goetz <brian.go...@oracle.com> wrote: > > Here's one that I suspect we're not expecting to recover terribly well > from: hierarchy inversion. Suppose at compile time A <: B. So the > following is a sensible switch body: > > case String: println("String"); break; > case Object: println("Object"); break; > > Now, imagine that by runtime, String no longer extends Object, but > instead Object absurdly extends String. Do we still expect the above to > print String for all Strings, and Object for everything else? Or is the > latter arm now dead at runtime, even though it wouldn't compile after > the change? Or is this now UB, because it would no longer compile? I'm still giving thought to everything you've written, but I am wondering: How feasible is it to get the above to fail early with an informative exception/Error? -- Mark Raynsford | http://www.io7m.com
Re: Expression switch exception naming
On 2018-03-30T16:17:35 -0400 Brian Goetz <brian.go...@oracle.com> wrote: > > OK, we have a terminology confusion over the term "exhaustiveness > checking." Got it, I'm up to speed! > But, your point is taken; *not* having a default in a situation that > requires exhaustiveness acts as a type-check on that exhaustiveness, and > saying default will then cover up any sins. I get it. Yep, that's the bit I'd hate to lose (not that I actually have it right now :]). -- Mark Raynsford | http://www.io7m.com
Re: Expression switch exception naming
On 2018-03-30T14:39:30 -0400 Brian Goetz <brian.go...@oracle.com> wrote: > > To be clear, I was describing: > - We'd always do exhaustiveness checking for expression switches > - A default / total pattern always implies exhaustive > - We'd additionally consider an expression switch to be exhaustive if > all known enums are present _and_ the enum type is in the same module as > the switch > > But that's probably too fussy. That seems rather unpleasant: If my API returns values of a sealed type and I expect API consumers to match on/switch on values of that type (consider something like Scala's Either type), it'd be very nasty if they didn't get exhaustiveness checks just because the consumers live outside of my module. Perhaps I've misunderstood and that wasn't what was intended? Additionally... If we're tying things to modules, what will happen to OSGi? The module system there isn't integrated with the JPMS in any sense yet. I suppose you could sort of argue that the entire OSGi system lives in the unnamed module, but ... -- Mark Raynsford | http://www.io7m.com
Re: Expression switch exception naming
On 2018-03-28T14:15:33 +0100 Gavin Bierman <gavin.bier...@oracle.com> wrote: > > A worry here might be that other future features might want to raise this > exception, and the name will be less applicable. Perhaps > "UnexpectedValueException"? Is this really a worry? I mean, it's not really as if unchecked exception types are in short supply. I'd be in favour of adding more and more specific exceptions so that instances of them indicated *exactly* what went wrong (rather than "one of these several different language constructs went wrong in some way"). -- Mark Raynsford | http://www.io7m.com
Re: break seen as a C archaism
On 2018-03-15T14:50:45 -0400 Brian Goetz <brian.go...@oracle.com> wrote: > > > If you are reconsidering options, reconsider "yield", meaning > >"break current context with this value". > > Still feeling a little burned by first time we floated this, but willing > to try another run up the flagpole Silly idea, but... *puts on fireproof suit*: "finally x;" -- Mark Raynsford | http://www.io7m.com
Re: Record construction
On 2018-03-14T13:10:16 -0400 Brian Goetz <brian.go...@oracle.com> wrote: > > One possibility, as discussed, is to attach these as their own thing: > "require x > 0", which is code that is narrowly targeted enough to lift. > > Another is to raise the Preconditions library to more than just a > library... To me, it seems like the former would be better in general: A boolean-valued expression, transformed to a string in a similar manner to assertions in order to provide a nice error message. I think the bytecode would probably need to wrap any exception raised by the expressions in an IllegalStateException that distinguishes it from an exception raised due to the check failing (I think a check raising its own exception should be considered to be a bug). I'd personally not want to see any particular precondition library have elevated status. Various libraries are designed to work in different environments. For example, I wrote a small thing that I tend to use everywhere now that's designed to be zero-allocation in the case of non-failing checks: https://io7m.github.io/jaffirm/ If, for example, the Preconditions library didn't make this guarantee, then I'd be hesitant to use preconditions on records at all in any code that did want that zero-allocation guarantee. The point is less about one API being preferred over another, and more about making sure that people actually do put validation everywhere because we can give assurances that the performance is good. > Either way, though, I think better support for DBC is an orthogonal > feature; I don't think it significantly constrains our ability to > deliver a records feature without it. Probably not, no. I must admit that the handling of non-null record arguments prompted me to bring this up (because adding Objects.requireNonNull() to every class I ever write adds up to a lot of boilerplate). I need to go back and read up on the current status of null handling, but I feel like it'd be nice if it could be handled via a generic "requires" mechanism along with all of the other checks - if we don't have a more terse way to say "this field must never be null" in the field declaration, of course! -- Mark Raynsford | http://www.io7m.com
Re: Record construction
On 2018-03-14T10:55:54 -0400 Brian Goetz <brian.go...@oracle.com> wrote: > > The constructor syntax > > record Point(int x, int y) { > Point { > } > } > > is proposed as a shorthand for an explicit default constructor: > > record Point(int x, int y) { > Point(int x, int y) { > } > } > One small thing: Could the contents of this constructor be lifted into JavaDoc? The fact that preconditions are supposed to appear in documentation is something that seems to be sadly lacking in almost all of the "design by contract" systems for Java. If I write a constructor like: record Point(int x, int y) { Point { Preconditions.mustBeNonNegative(x); Preconditions.mustBeNonNegative(y); } } It'd be nice if those statements could be made visible in the JavaDoc. I think this was covered slightly in some of the other discussion about "requires", but it fizzled out (unless I missed something). -- Mark Raynsford | http://www.io7m.com
Re: [patterns] AND patterns, OR patterns, fall though
On 2017-11-04T18:20:44 -0400 Brian Goetz <brian.go...@oracle.com> wrote: > In theory, patterns can be combined with AND and OR to produce new patterns, The OR patterns do indeed look good. I'd have to play with a prototype to see what works and what doesn't though. -- Mark Raynsford | http://www.io7m.com
Re: PM design question: Scopes
On 2017-11-03T10:44:51 + Gavin Bierman <gavin.bier...@oracle.com> wrote: > > switch (o) { > case int i : ... // in scope and DA > ... // in scope and DA > case T i : // int i not in scope, so can re-use > } > ... // i not in scope > +ve Simple syntactic rule > +ve Allows reuse of pattern variable in the same switch statement. > -ve Doesn’t make sense for fallthrough > > NOTE This final point is important - supporting fallthrough impacts on what > solution we might choose for scoping of pattern variables. (We could not > support fallthrough and instead support OR patterns - a further design > dimension.) I'm strongly in favour of this one. In my experience and opinion, fallthrough was a mistake in C and it was a mistake to copy it in Java. In some 15 years and close to a million lines of code, I have never once felt the need to fall through a switch case. If fallthrough didn't exist, I'm not sure that there'd even be a question that this was the "right" choice... It essentially makes each branch of the switch appear to match the scoping rules for lambdas. From a pedagogical standpoint, all of the other languages I know of that have implemented pattern matching never had any kind of fallthrough in the first place, so it'd likely benefit Java to match them (no pun intended!). I'm thinking of people who learned pattern matching via something like ML being able to write Java switches/matches without having to make the mental gear change of inserting these annoying "break" statements everywhere. Is there actually a significant amount of code out there that uses switch case fallthrough? I mean, I know you have to assume that there is in the interest of preserving backwards compatibility, but I'm curious if there are actually any metrics on this. I've never even seen fallthrough in any code in the wild... I was under the impression that modern IDEs warn against fallthroughs by default. -- Mark Raynsford | http://www.io7m.com
Re: Data classes
On 2017-11-02T11:05:33 -0400 Brian Goetz <brian.go...@oracle.com> wrote: > > In the proposal, you discuss about adding keywords like "non-final, > > unfinal, mutable", i.e. considering that there can be a different set of > > keywords for data class declaration which is different from the one we have > > on fields. In that spirit, we can have a flag like nullable, maybenull, etc > > to specify that the compiler will not generate a requireNonNull inside the > > principal constructor and that equals or hashCode do not need nullchecks. > > Not really a fair comparison. The keywords suggested like "non-final" > are not adding new concepts; they are merely making explicit something > that was previously implicit. They are more akin to allowing the > "package" keyword to describe the default accessibility of class fields, > rather than a new feature. Non-nullability, on the other hand, is indeed > a new feature. And, it is not a simple property of fields (well, it > could be, but I suspect users would find that to an unsatisfying > interpretation of "support for non-nullity.") I think it would be better to get non-nullability language-wide first as a separate feature. -- Mark Raynsford | http://www.io7m.com
Re: Data classes
On 2017-11-01T14:53:40 -0400 Brian Goetz <brian.go...@oracle.com> wrote: > At the following URL, please find a writeup containing our current > thoughts on Data Classes for Java: > > http://cr.openjdk.java.net/~briangoetz/amber/datum.html > > Comments welcome! > > We'll be making a prototype available soon for folks to play with. This looks great! One thing springs to mind with the accessors: Is it possible that the generated accessor methods could participate in the implementation of interfaces? For example: interface Vector { double x(); double y(); double z(); } __data class Vector3 (double x, double y, double z) implements Vector { } The Vector3 type would automatically get x(), y(), and z() methods based on the field declarations. This may imply, of course, that the fields are public by default. I don't see any obvious reasons why this couldn't work, but you've almost certainly thought further than I have on the subject. -- Mark Raynsford | http://www.io7m.com
Re: My lambda leftovers
On 2017-10-06T13:16:19 +0200 Remi Forax <fo...@univ-mlv.fr> wrote: > > - allow void to be converted to Void (by returning null) > This may not be *too* bad as long as it's strictly limited to Void, because presumably a compliant program should never have had an observable non-null value of type Void. What I was wondering is if it might be time to introduce a strictly non-null single-valued Unit (value) type. This would unfortunately be incompatible with existing Void-typed methods... -- Mark Raynsford | http://www.io7m.com