From forax at univ-mlv.fr Mon Dec 4 13:34:23 2017 From: forax at univ-mlv.fr (Remi Forax) Date: Mon, 4 Dec 2017 14:34:23 +0100 (CET) Subject: compareTo and deriving Message-ID: <1785948989.404045.1512394463315.JavaMail.zimbra@u-pem.fr> Ok, record/data class currently provides getters and toString/equals/hashCode, what if i want to add a compareTo. In Haskell, it's easy, one can use 'deriving', in Java, it can be written that way, record Point(int x, int y) implements Comparable deriving equals, hashCode, toString, compareTo; For the compiler, the method exists in the supertypes, so the signature of the method can be computed from the super types, after each generated method use an invokedynamic with the same meta protocol (a list of getters), so if by convention "equals", "hashCode", "toString", "compareTo" are the names of some bootstrap methods in a class DerivingMetaFactory, it can be trivial for a JDK developer to add a new bootstrap method without having to change the JLS. So my point is, that we can specify once how the compiler should generate codes for any generated methods in an extensible way. regards, R?mi From brian.goetz at oracle.com Mon Dec 4 14:54:13 2017 From: brian.goetz at oracle.com (Brian Goetz) Date: Mon, 4 Dec 2017 09:54:13 -0500 Subject: compareTo and deriving In-Reply-To: <1785948989.404045.1512394463315.JavaMail.zimbra@u-pem.fr> References: <1785948989.404045.1512394463315.JavaMail.zimbra@u-pem.fr> Message-ID: <95c85f87-0624-0531-e490-dd28b301bbbb@oracle.com> As you might remember from the EG meeting, we have toyed with a notion of "method builders" which would be a more declarative way to declare template-izable methods.? The intent all along was that, once we have such a mechanism, records would just be a consumer of such a mechanism. That said, such a mechanism is bigger both from a technical perspective and a bikeshed perspective.? 90% of the current OO-ceremony-pain is in ctors, Object methods, and accessors.? So my preference would be to nail down the semantics of data classes first, and then explore whether it makes sense to surface a more general mechanism. On 12/4/2017 8:34 AM, Remi Forax wrote: > Ok, > record/data class currently provides getters and toString/equals/hashCode, what if i want to add a compareTo. > > In Haskell, it's easy, one can use 'deriving', in Java, it can be written that way, > record Point(int x, int y) implements Comparable > deriving equals, hashCode, toString, compareTo; > > For the compiler, the method exists in the supertypes, so the signature of the method can be computed from the super types, > after each generated method use an invokedynamic with the same meta protocol (a list of getters), > so if by convention "equals", "hashCode", "toString", "compareTo" are the names of some bootstrap methods in a class DerivingMetaFactory, > it can be trivial for a JDK developer to add a new bootstrap method without having to change the JLS. > > So my point is, that we can specify once how the compiler should generate codes for any generated methods in an extensible way. > > regards, > R?mi > > From forax at univ-mlv.fr Mon Dec 4 15:12:58 2017 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Mon, 4 Dec 2017 16:12:58 +0100 (CET) Subject: compareTo and deriving In-Reply-To: <95c85f87-0624-0531-e490-dd28b301bbbb@oracle.com> References: <1785948989.404045.1512394463315.JavaMail.zimbra@u-pem.fr> <95c85f87-0624-0531-e490-dd28b301bbbb@oracle.com> Message-ID: <2014433279.467778.1512400378168.JavaMail.zimbra@u-pem.fr> ----- Mail original ----- > De: "Brian Goetz" > ?: "Remi Forax" , "amber-spec-experts" > Envoy?: Lundi 4 D?cembre 2017 15:54:13 > Objet: Re: compareTo and deriving > As you might remember from the EG meeting, we have toyed with a notion > of "method builders" which would be a more declarative way to declare > template-izable methods.? The intent all along was that, once we have > such a mechanism, records would just be a consumer of such a mechanism. > > That said, such a mechanism is bigger both from a technical perspective > and a bikeshed perspective.? 90% of the current OO-ceremony-pain is in > ctors, Object methods, and accessors.? So my preference would be to nail > down the semantics of data classes first, and then explore whether it > makes sense to surface a more general mechanism. I'm trying to propose a middle ground (not too far from only support Object methods) to allow to not have too much couple between the JLS and the metafactory methods. One advantage i see to have compareTo generated is that most implementation i see of the compareTo are broken either because of the primitive overflowing or compareTo and equals not having compatible semantics. One problem we will have if we let people to write compareTo is that it will be harder to get it right because equals will be generated while compareTo will be written manually without seeing the code of equals. R?mi > > > On 12/4/2017 8:34 AM, Remi Forax wrote: >> Ok, >> record/data class currently provides getters and toString/equals/hashCode, what >> if i want to add a compareTo. >> >> In Haskell, it's easy, one can use 'deriving', in Java, it can be written that >> way, >> record Point(int x, int y) implements Comparable >> deriving equals, hashCode, toString, compareTo; >> >> For the compiler, the method exists in the supertypes, so the signature of the >> method can be computed from the super types, >> after each generated method use an invokedynamic with the same meta protocol (a >> list of getters), >> so if by convention "equals", "hashCode", "toString", "compareTo" are the names >> of some bootstrap methods in a class DerivingMetaFactory, >> it can be trivial for a JDK developer to add a new bootstrap method without >> having to change the JLS. >> >> So my point is, that we can specify once how the compiler should generate codes >> for any generated methods in an extensible way. >> >> regards, >> R?mi >> From brian.goetz at oracle.com Mon Dec 4 15:20:56 2017 From: brian.goetz at oracle.com (Brian Goetz) Date: Mon, 4 Dec 2017 10:20:56 -0500 Subject: compareTo and deriving In-Reply-To: <2014433279.467778.1512400378168.JavaMail.zimbra@u-pem.fr> References: <1785948989.404045.1512394463315.JavaMail.zimbra@u-pem.fr> <95c85f87-0624-0531-e490-dd28b301bbbb@oracle.com> <2014433279.467778.1512400378168.JavaMail.zimbra@u-pem.fr> Message-ID: <65189507-cf3a-1aef-1fbf-0f228a89db8e@oracle.com> Let's not forget that compareTo() got much easier to implement with the advent of the Comparator factories in Java 8: ??? static final Comparator c ??????? = Comparators.comparing(Foo::int).thenComparing(Foo::bar); ??? public int compareTo(T other) { return c.compare(this, other); } On 12/4/2017 10:12 AM, forax at univ-mlv.fr wrote: > ----- Mail original ----- >> De: "Brian Goetz" >> ?: "Remi Forax" , "amber-spec-experts" >> Envoy?: Lundi 4 D?cembre 2017 15:54:13 >> Objet: Re: compareTo and deriving >> As you might remember from the EG meeting, we have toyed with a notion >> of "method builders" which would be a more declarative way to declare >> template-izable methods.? The intent all along was that, once we have >> such a mechanism, records would just be a consumer of such a mechanism. >> >> That said, such a mechanism is bigger both from a technical perspective >> and a bikeshed perspective.? 90% of the current OO-ceremony-pain is in >> ctors, Object methods, and accessors.? So my preference would be to nail >> down the semantics of data classes first, and then explore whether it >> makes sense to surface a more general mechanism. > I'm trying to propose a middle ground (not too far from only support Object methods) to allow to not have too much couple between the JLS and the metafactory methods. > > One advantage i see to have compareTo generated is that most implementation i see of the compareTo are broken either because of the primitive overflowing or compareTo and equals not having compatible semantics. One problem we will have if we let people to write compareTo is that it will be harder to get it right because equals will be generated while compareTo will be written manually without seeing the code of equals. > > R?mi > >> >> On 12/4/2017 8:34 AM, Remi Forax wrote: >>> Ok, >>> record/data class currently provides getters and toString/equals/hashCode, what >>> if i want to add a compareTo. >>> >>> In Haskell, it's easy, one can use 'deriving', in Java, it can be written that >>> way, >>> record Point(int x, int y) implements Comparable >>> deriving equals, hashCode, toString, compareTo; >>> >>> For the compiler, the method exists in the supertypes, so the signature of the >>> method can be computed from the super types, >>> after each generated method use an invokedynamic with the same meta protocol (a >>> list of getters), >>> so if by convention "equals", "hashCode", "toString", "compareTo" are the names >>> of some bootstrap methods in a class DerivingMetaFactory, >>> it can be trivial for a JDK developer to add a new bootstrap method without >>> having to change the JLS. >>> >>> So my point is, that we can specify once how the compiler should generate codes >>> for any generated methods in an extensible way. >>> >>> regards, >>> R?mi >>> From forax at univ-mlv.fr Tue Dec 5 07:59:10 2017 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Tue, 5 Dec 2017 08:59:10 +0100 (CET) Subject: compareTo and deriving In-Reply-To: <65189507-cf3a-1aef-1fbf-0f228a89db8e@oracle.com> References: <1785948989.404045.1512394463315.JavaMail.zimbra@u-pem.fr> <95c85f87-0624-0531-e490-dd28b301bbbb@oracle.com> <2014433279.467778.1512400378168.JavaMail.zimbra@u-pem.fr> <65189507-cf3a-1aef-1fbf-0f228a89db8e@oracle.com> Message-ID: <948443041.647371.1512460750221.JavaMail.zimbra@u-pem.fr> yes. R?mi ----- Mail original ----- > De: "Brian Goetz" > ?: forax at univ-mlv.fr > Cc: "amber-spec-experts" > Envoy?: Lundi 4 D?cembre 2017 16:20:56 > Objet: Re: compareTo and deriving > Let's not forget that compareTo() got much easier to implement with the > advent of the Comparator factories in Java 8: > > ??? static final Comparator c > ??????? = Comparators.comparing(Foo::int).thenComparing(Foo::bar); > > ??? public int compareTo(T other) { return c.compare(this, other); } > > > > On 12/4/2017 10:12 AM, forax at univ-mlv.fr wrote: >> ----- Mail original ----- >>> De: "Brian Goetz" >>> ?: "Remi Forax" , "amber-spec-experts" >>> >>> Envoy?: Lundi 4 D?cembre 2017 15:54:13 >>> Objet: Re: compareTo and deriving >>> As you might remember from the EG meeting, we have toyed with a notion >>> of "method builders" which would be a more declarative way to declare >>> template-izable methods.? The intent all along was that, once we have >>> such a mechanism, records would just be a consumer of such a mechanism. >>> >>> That said, such a mechanism is bigger both from a technical perspective >>> and a bikeshed perspective.? 90% of the current OO-ceremony-pain is in >>> ctors, Object methods, and accessors.? So my preference would be to nail >>> down the semantics of data classes first, and then explore whether it >>> makes sense to surface a more general mechanism. >> I'm trying to propose a middle ground (not too far from only support Object >> methods) to allow to not have too much couple between the JLS and the >> metafactory methods. >> >> One advantage i see to have compareTo generated is that most implementation i >> see of the compareTo are broken either because of the primitive overflowing or >> compareTo and equals not having compatible semantics. One problem we will have >> if we let people to write compareTo is that it will be harder to get it right >> because equals will be generated while compareTo will be written manually >> without seeing the code of equals. >> >> R?mi >> >>> >>> On 12/4/2017 8:34 AM, Remi Forax wrote: >>>> Ok, >>>> record/data class currently provides getters and toString/equals/hashCode, what >>>> if i want to add a compareTo. >>>> >>>> In Haskell, it's easy, one can use 'deriving', in Java, it can be written that >>>> way, >>>> record Point(int x, int y) implements Comparable >>>> deriving equals, hashCode, toString, compareTo; >>>> >>>> For the compiler, the method exists in the supertypes, so the signature of the >>>> method can be computed from the super types, >>>> after each generated method use an invokedynamic with the same meta protocol (a >>>> list of getters), >>>> so if by convention "equals", "hashCode", "toString", "compareTo" are the names >>>> of some bootstrap methods in a class DerivingMetaFactory, >>>> it can be trivial for a JDK developer to add a new bootstrap method without >>>> having to change the JLS. >>>> >>>> So my point is, that we can specify once how the compiler should generate codes >>>> for any generated methods in an extensible way. >>>> >>>> regards, >>>> R?mi From forax at univ-mlv.fr Tue Dec 5 08:12:34 2017 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Tue, 5 Dec 2017 09:12:34 +0100 (CET) Subject: compareTo and deriving In-Reply-To: <2014433279.467778.1512400378168.JavaMail.zimbra@u-pem.fr> References: <1785948989.404045.1512394463315.JavaMail.zimbra@u-pem.fr> <95c85f87-0624-0531-e490-dd28b301bbbb@oracle.com> <2014433279.467778.1512400378168.JavaMail.zimbra@u-pem.fr> Message-ID: <1381439255.655271.1512461554364.JavaMail.zimbra@u-pem.fr> So let restart by trying to untangle things. First question, should we only support Object methods (toString, equals, hashCode) or more, again here, two sub questions, it can be only (toString, equals, hashCode) in the first release and more in a subsequent release. Why more, the question is where to put the bar, if we do only toString, equals, hashCode, people will ask for compareTo (i don't think there is another method than compareTo but i may be wrong). The rational to introduce compareTo is that the semantics of compareTo is linked to the semantics of equals which is generated, so asking someone to write compareTo given that he doesn't have to write equals is clunky. There are several reason to not introduce compareTo. toString, equals, hashCode cover most of the case and we have to stop somewhere, people tend to use compareTo at a place where they should use a Comparator, i.e. the order defined by compareTo is not a 'natural' order. Second question, how to specify the mapping between toString, equals, hashCode and their implementation ? Should we have a way to extends the mapping if we want to introduce compareTo later without having to change the JLS. Again, sub-questions, Should this mapping being explicit like in Haskell with deriving or should it be implicit, there is no real need to specify a mapping for the method for j.l.Object because those method declaration are already present, and if the record implements Comparable it means that we have to provide the implementation of compareTo. regards, R?mi ----- Mail original ----- > De: forax at univ-mlv.fr > ?: "Brian Goetz" > Cc: "amber-spec-experts" > Envoy?: Lundi 4 D?cembre 2017 16:12:58 > Objet: Re: compareTo and deriving > ----- Mail original ----- >> De: "Brian Goetz" >> ?: "Remi Forax" , "amber-spec-experts" >> >> Envoy?: Lundi 4 D?cembre 2017 15:54:13 >> Objet: Re: compareTo and deriving > >> As you might remember from the EG meeting, we have toyed with a notion >> of "method builders" which would be a more declarative way to declare >> template-izable methods.? The intent all along was that, once we have >> such a mechanism, records would just be a consumer of such a mechanism. >> >> That said, such a mechanism is bigger both from a technical perspective >> and a bikeshed perspective.? 90% of the current OO-ceremony-pain is in >> ctors, Object methods, and accessors.? So my preference would be to nail >> down the semantics of data classes first, and then explore whether it >> makes sense to surface a more general mechanism. > > I'm trying to propose a middle ground (not too far from only support Object > methods) to allow to not have too much couple between the JLS and the > metafactory methods. > > One advantage i see to have compareTo generated is that most implementation i > see of the compareTo are broken either because of the primitive overflowing or > compareTo and equals not having compatible semantics. One problem we will have > if we let people to write compareTo is that it will be harder to get it right > because equals will be generated while compareTo will be written manually > without seeing the code of equals. > > R?mi > >> >> >> On 12/4/2017 8:34 AM, Remi Forax wrote: >>> Ok, >>> record/data class currently provides getters and toString/equals/hashCode, what >>> if i want to add a compareTo. >>> >>> In Haskell, it's easy, one can use 'deriving', in Java, it can be written that >>> way, >>> record Point(int x, int y) implements Comparable >>> deriving equals, hashCode, toString, compareTo; >>> >>> For the compiler, the method exists in the supertypes, so the signature of the >>> method can be computed from the super types, >>> after each generated method use an invokedynamic with the same meta protocol (a >>> list of getters), >>> so if by convention "equals", "hashCode", "toString", "compareTo" are the names >>> of some bootstrap methods in a class DerivingMetaFactory, >>> it can be trivial for a JDK developer to add a new bootstrap method without >>> having to change the JLS. >>> >>> So my point is, that we can specify once how the compiler should generate codes >>> for any generated methods in an extensible way. >>> >>> regards, >>> R?mi From brian.goetz at oracle.com Tue Dec 5 15:17:32 2017 From: brian.goetz at oracle.com (Brian Goetz) Date: Tue, 5 Dec 2017 10:17:32 -0500 Subject: compareTo and deriving In-Reply-To: <1381439255.655271.1512461554364.JavaMail.zimbra@u-pem.fr> References: <1785948989.404045.1512394463315.JavaMail.zimbra@u-pem.fr> <95c85f87-0624-0531-e490-dd28b301bbbb@oracle.com> <2014433279.467778.1512400378168.JavaMail.zimbra@u-pem.fr> <1381439255.655271.1512461554364.JavaMail.zimbra@u-pem.fr> Message-ID: <70794241-5999-fec3-bf04-8276948592ed@oracle.com> At some level, this goes to the heart of "why should records, and not tuples, be our 'plain data' abstraction."? Adding behavior to a record, **where that behavior is derived strictly from the state**, is easy and natural. You can look at the "but I have to write compareTo" issue as glass 90% full or 10% empty; a record starts life with a sensible ctor, dtor, accessors, equals, hashCode, and toString.? So adding compareTo is pretty easy.? Note also that there's no way we can reasonably derive compareTo from the state description, since (a) not all state fields are going to be relevant for ordering comparison and (b) the order in which they are declared may not be the natural comparison order.? So somewhere in the program the user is going to have to write down that "a Person is compared by (lastName, firstName)", somehow. We could of course invent all sorts of shorthands, but I think the return-on-complexity there is limited, as the natural way to say this is pretty straightforward: ??? record Person(String first, String last, String age) ??????????? implements Comparable { ??????? private static final Comparator cc ??????????? = Comparators.comparing(Person::last).thenComparing(Person::first); ??????? public int compareTo(Person other) { ??????????? return cc.compareTo(other); ??????? } ??? } But, let's not get distracted by Billy, we care about semantics. Where are the pitfalls you see of this approach where it might run afoul of the "consistency between equals and compareTo is recommended" dictum? On 12/5/2017 3:12 AM, forax at univ-mlv.fr wrote: > So let restart by trying to untangle things. > > First question, should we only support Object methods (toString, equals, hashCode) or more, again here, two sub questions, > it can be only (toString, equals, hashCode) in the first release and more in a subsequent release. > Why more, the question is where to put the bar, if we do only toString, equals, hashCode, people will ask for compareTo (i don't think there is another method than compareTo but i may be wrong). The rational to introduce compareTo is that the semantics of compareTo is linked to the semantics of equals which is generated, so asking someone to write compareTo given that he doesn't have to write equals is clunky. There are several reason to not introduce compareTo. toString, equals, hashCode cover most of the case and we have to stop somewhere, people tend to use compareTo at a place where they should use a Comparator, i.e. the order defined by compareTo is not a 'natural' order. > > Second question, how to specify the mapping between toString, equals, hashCode and their implementation ? Should we have a way to extends the mapping if we want to introduce compareTo later without having to change the JLS. Again, sub-questions, Should this mapping being explicit like in Haskell with deriving or should it be implicit, there is no real need to specify a mapping for the method for j.l.Object because those method declaration are already present, and if the record implements Comparable it means that we have to provide the implementation of compareTo. > > regards, > R?mi > > ----- Mail original ----- >> De: forax at univ-mlv.fr >> ?: "Brian Goetz" >> Cc: "amber-spec-experts" >> Envoy?: Lundi 4 D?cembre 2017 16:12:58 >> Objet: Re: compareTo and deriving >> ----- Mail original ----- >>> De: "Brian Goetz" >>> ?: "Remi Forax" , "amber-spec-experts" >>> >>> Envoy?: Lundi 4 D?cembre 2017 15:54:13 >>> Objet: Re: compareTo and deriving >>> As you might remember from the EG meeting, we have toyed with a notion >>> of "method builders" which would be a more declarative way to declare >>> template-izable methods.? The intent all along was that, once we have >>> such a mechanism, records would just be a consumer of such a mechanism. >>> >>> That said, such a mechanism is bigger both from a technical perspective >>> and a bikeshed perspective.? 90% of the current OO-ceremony-pain is in >>> ctors, Object methods, and accessors.? So my preference would be to nail >>> down the semantics of data classes first, and then explore whether it >>> makes sense to surface a more general mechanism. >> I'm trying to propose a middle ground (not too far from only support Object >> methods) to allow to not have too much couple between the JLS and the >> metafactory methods. >> >> One advantage i see to have compareTo generated is that most implementation i >> see of the compareTo are broken either because of the primitive overflowing or >> compareTo and equals not having compatible semantics. One problem we will have >> if we let people to write compareTo is that it will be harder to get it right >> because equals will be generated while compareTo will be written manually >> without seeing the code of equals. >> >> R?mi >> >>> >>> On 12/4/2017 8:34 AM, Remi Forax wrote: >>>> Ok, >>>> record/data class currently provides getters and toString/equals/hashCode, what >>>> if i want to add a compareTo. >>>> >>>> In Haskell, it's easy, one can use 'deriving', in Java, it can be written that >>>> way, >>>> record Point(int x, int y) implements Comparable >>>> deriving equals, hashCode, toString, compareTo; >>>> >>>> For the compiler, the method exists in the supertypes, so the signature of the >>>> method can be computed from the super types, >>>> after each generated method use an invokedynamic with the same meta protocol (a >>>> list of getters), >>>> so if by convention "equals", "hashCode", "toString", "compareTo" are the names >>>> of some bootstrap methods in a class DerivingMetaFactory, >>>> it can be trivial for a JDK developer to add a new bootstrap method without >>>> having to change the JLS. >>>> >>>> So my point is, that we can specify once how the compiler should generate codes >>>> for any generated methods in an extensible way. >>>> >>>> regards, >>>> R?mi From guy.steele at oracle.com Tue Dec 5 16:29:42 2017 From: guy.steele at oracle.com (Guy Steele) Date: Tue, 5 Dec 2017 11:29:42 -0500 Subject: compareTo and deriving In-Reply-To: <70794241-5999-fec3-bf04-8276948592ed@oracle.com> References: <1785948989.404045.1512394463315.JavaMail.zimbra@u-pem.fr> <95c85f87-0624-0531-e490-dd28b301bbbb@oracle.com> <2014433279.467778.1512400378168.JavaMail.zimbra@u-pem.fr> <1381439255.655271.1512461554364.JavaMail.zimbra@u-pem.fr> <70794241-5999-fec3-bf04-8276948592ed@oracle.com> Message-ID: > On Dec 5, 2017, at 10:17 AM, Brian Goetz wrote: > > At some level, this goes to the heart of "why should records, and not tuples, be our 'plain data' abstraction." Adding behavior to a record, **where that behavior is derived strictly from the state**, is easy and natural. > > You can look at the "but I have to write compareTo" issue as glass 90% full or 10% empty; a record starts life with a sensible ctor, dtor, accessors, equals, hashCode, and toString. So adding compareTo is pretty easy. Note also that there's no way we can reasonably derive compareTo from the state description, since (a) not all state fields are going to be relevant for ordering comparison and (b) the order in which they are declared may not be the natural comparison order. So somewhere in the program the user is going to have to write down that "a Person is compared by (lastName, firstName)", somehow. > > We could of course invent all sorts of shorthands, but I think the return-on-complexity there is limited, as the natural way to say this is pretty straightforward: > > record Person(String first, String last, String age) > implements Comparable { > > private static final Comparator cc > = Comparators.comparing(Person::last).thenComparing(Person::first); > > public int compareTo(Person other) { > return cc.compareTo(other); > } > } > > But, let's not get distracted by Billy, we care about semantics. Where are the pitfalls you see of this approach where it might run afoul of the "consistency between equals and compareTo is recommended" dictum? If the automatically generated `equals` method examines all fields but the `compareTo` method does not, then there can be cases where `compareTo` says that two objects are equal but `equals` says they are unequal. Example: Person me = new Person(?Guy?, ?Steele?, 63); Person myDad = new Person(?Guy?, ?Steele?, 89); me.equals(myDad) ==> false me.compareTo(myDad) ==> 0 One possible solution is simply a programming discipline or documentation suggestion that says that if you write a `compareTo` method, with or without a `Comparator`, you should at least think about whether to provide an explicit definition of `equals` as well to ensure that they are consistent. In fact, we already have such a note in the documentation for `Comparator`: The natural ordering for a class C is said to be consistent with equals if and only if e1.compareTo(e2) == 0 has the same boolean value as e1.equals(e2) for every e1 and e2 of class C. . . . It is strongly recommended (though not required) that natural orderings be consistent with equals. . . . Virtually all Java core classes that implement Comparable have natural orderings that are consistent with equals. So I think the question that R?mi raises has to do with whether the language design going forward can/should better assist programmers to address this already existing recommendation. ?Guy From forax at univ-mlv.fr Wed Dec 6 15:10:26 2017 From: forax at univ-mlv.fr (Remi Forax) Date: Wed, 6 Dec 2017 16:10:26 +0100 (CET) Subject: primary fields, reflection and annotations Message-ID: <1721759314.1398988.1512573026386.JavaMail.zimbra@u-pem.fr> I suppose it's a good idea to support annotations on record fields, record Person(@JSONProperty String name); at least the current prototype support that, it considers that the target is an ElementType.FIELD and copy the annotations to the generated fields. To access to the annotation sat runtime, you have to crawle the declared fields of the record which are private. And those annotations are not present as parameter annotation of the primary constructor. I think we should consider how we want to reflect on a record Class, Do we want a java.lang.reflect.RecordField ? Do we want a new annotation target RECORD_FIELD ? regards, R?mi From brian.goetz at oracle.com Wed Dec 6 15:20:01 2017 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 6 Dec 2017 10:20:01 -0500 Subject: primary fields, reflection and annotations In-Reply-To: <1721759314.1398988.1512573026386.JavaMail.zimbra@u-pem.fr> References: <1721759314.1398988.1512573026386.JavaMail.zimbra@u-pem.fr> Message-ID: By default, the fields should not be private, but package-access, like other classes.? (You are permitted to upgrade them to public.) But let's ignore the details for a minute, and ask: is a (non-type) annotation on a "field" of a record just a field annotation, or is it something more? On 12/6/2017 10:10 AM, Remi Forax wrote: > I suppose it's a good idea to support annotations on record fields, > record Person(@JSONProperty String name); > at least the current prototype support that, it considers that the target is an ElementType.FIELD and copy the annotations to the generated fields. > > To access to the annotation sat runtime, you have to crawle the declared fields of the record which are private. > And those annotations are not present as parameter annotation of the primary constructor. > > I think we should consider how we want to reflect on a record Class, > Do we want a java.lang.reflect.RecordField ? > Do we want a new annotation target RECORD_FIELD ? > > regards, > R?mi > From kevinb at google.com Wed Dec 6 15:42:19 2017 From: kevinb at google.com (Kevin Bourrillion) Date: Wed, 6 Dec 2017 10:42:19 -0500 Subject: primary fields, reflection and annotations In-Reply-To: References: <1721759314.1398988.1512573026386.JavaMail.zimbra@u-pem.fr> Message-ID: Ideally, if the annotation has ElementType.FIELD, it would get copied to the field, and also, if it has ElementType.METHOD, it would get copied to the accessor method. Of course if it's TYPE_USE then it's part of the type and should go everywhere the type goes. Lastly, if it has PARAMETER I would certainly expect it to be findable on the constructor itself. I would intuitively expect all these things to work without any need for ElementType.RECORD_FIELD. On Wed, Dec 6, 2017 at 10:20 AM, Brian Goetz wrote: > By default, the fields should not be private, but package-access, like > other classes. (You are permitted to upgrade them to public.) > > But let's ignore the details for a minute, and ask: is a (non-type) > annotation on a "field" of a record just a field annotation, or is it > something more? > > > On 12/6/2017 10:10 AM, Remi Forax wrote: > >> I suppose it's a good idea to support annotations on record fields, >> record Person(@JSONProperty String name); >> at least the current prototype support that, it considers that the target >> is an ElementType.FIELD and copy the annotations to the generated fields. >> >> To access to the annotation sat runtime, you have to crawle the declared >> fields of the record which are private. >> And those annotations are not present as parameter annotation of the >> primary constructor. >> >> I think we should consider how we want to reflect on a record Class, >> Do we want a java.lang.reflect.RecordField ? >> Do we want a new annotation target RECORD_FIELD ? >> >> regards, >> R?mi >> >> > -- Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb at google.com -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Wed Dec 6 18:07:42 2017 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 6 Dec 2017 13:07:42 -0500 Subject: [records] equality on float / double components Message-ID: <9f776a91-8aab-c2d4-0fe2-ffa648c4fef4@oracle.com> It's time to play everyone's favorite game show, "What about NaN". If we have a record: record Foo(float f); We would like Object.equals() to be reflexive, symmetric, and transitive. But if we define equals() in the obvious way (delegating to float==), then `new Foo(Float.NaN`) would not be equal to itself. If we delegate instead to `Float.compare(this.f, that.f)`, the NaN problem goes away (though comparison becomes modestly more expensive), but now +0 and -0 are distinguished (== treats them the same.) From kevinb at google.com Wed Dec 6 18:22:56 2017 From: kevinb at google.com (Kevin Bourrillion) Date: Wed, 6 Dec 2017 13:22:56 -0500 Subject: [records] equality on float / double components In-Reply-To: <9f776a91-8aab-c2d4-0fe2-ffa648c4fef4@oracle.com> References: <9f776a91-8aab-c2d4-0fe2-ffa648c4fef4@oracle.com> Message-ID: I'd strongly expect this to behave exactly as Float.equals() does. The +0.0/-0.0 problem exists but is nothing new. On Wed, Dec 6, 2017 at 1:07 PM, Brian Goetz wrote: > It's time to play everyone's favorite game show, "What about NaN". > > If we have a record: > > record Foo(float f); > > We would like Object.equals() to be reflexive, symmetric, and transitive. > But if we define equals() in the obvious way (delegating to float==), then > `new Foo(Float.NaN`) would not be equal to itself. > > If we delegate instead to `Float.compare(this.f, that.f)`, the NaN problem > goes away (though comparison becomes modestly more expensive), but now +0 > and -0 are distinguished (== treats them the same.) > > -- Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb at google.com -------------- next part -------------- An HTML attachment was scrubbed... URL: From guy.steele at oracle.com Wed Dec 6 18:10:07 2017 From: guy.steele at oracle.com (Guy Steele) Date: Wed, 6 Dec 2017 13:10:07 -0500 Subject: [records] equality on float / double components In-Reply-To: <9f776a91-8aab-c2d4-0fe2-ffa648c4fef4@oracle.com> References: <9f776a91-8aab-c2d4-0fe2-ffa648c4fef4@oracle.com> Message-ID: <8698C12D-070A-42B8-85AE-33B0323DB06E@oracle.com> > On Dec 6, 2017, at 1:07 PM, Brian Goetz wrote: > > It's time to play everyone's favorite game show, "What about NaN". > > If we have a record: > > record Foo(float f); > > We would like Object.equals() to be reflexive, symmetric, and transitive. But if we define equals() in the obvious way (delegating to float==), then `new Foo(Float.NaN`) would not be equal to itself. > > If we delegate instead to `Float.compare(this.f, that.f)`, the NaN problem goes away (though comparison becomes modestly more expensive), but now +0 and -0 are distinguished (== treats them the same.) This second choice (using FLoat.compare) is, IMHO, the correct one because it is consistent with the definition of Float.equals() (which goes back to JLS1), which was defined that way precisely because it ?allows hash tables to operate properly? (because it is an equivalence relation). If users want the behavior of `==` on, say, Point objects, then let them Do The Right Thing by providing an overloading of `==` for Point objects. (Oh, wait, ahem, cough, cough . . . :-) ?Guy P.S. The JDK8 documentation at https://docs.oracle.com/javase/8/docs/api/java/lang/Float.html#compare-float-float- has this INCORRECT summary of the return value for compareTo(Float) and compare(float,float): the value 0 if anotherFloat is numerically equal to this Float; a value less than 0 if this Float is numerically less than anotherFloat; and a value greater than 0 if this Float is numerically greater thananotherFloat. The more detailed descriptions speak the truth, but these summaries are in themselves incorrect and should be rewritten. The terms ?numerically equal (less, greater)? do not apply to these comparisons in all cases. -------------- next part -------------- An HTML attachment was scrubbed... URL: From paul.sandoz at oracle.com Wed Dec 6 18:49:08 2017 From: paul.sandoz at oracle.com (Paul Sandoz) Date: Wed, 6 Dec 2017 10:49:08 -0800 Subject: [records] equality on float / double components In-Reply-To: References: <9f776a91-8aab-c2d4-0fe2-ffa648c4fef4@oracle.com> Message-ID: <4A4F6845-0222-4F84-B8D8-D64E04B5B4ED@oracle.com> > On 6 Dec 2017, at 10:22, Kevin Bourrillion wrote: > > I'd strongly expect this to behave exactly as Float.equals() does. The +0.0/-0.0 problem exists but is nothing new. > I concur. Float/Double.equals/compare is also used for float/double[] array equals and compare. Paul. > > On Wed, Dec 6, 2017 at 1:07 PM, Brian Goetz > wrote: > It's time to play everyone's favorite game show, "What about NaN". > > If we have a record: > > record Foo(float f); > > We would like Object.equals() to be reflexive, symmetric, and transitive. But if we define equals() in the obvious way (delegating to float==), then `new Foo(Float.NaN`) would not be equal to itself. > > If we delegate instead to `Float.compare(this.f, that.f)`, the NaN problem goes away (though comparison becomes modestly more expensive), but now +0 and -0 are distinguished (== treats them the same.) > > > > > -- > Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb at google.com -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Wed Dec 6 18:51:38 2017 From: forax at univ-mlv.fr (Remi Forax) Date: Wed, 6 Dec 2017 19:51:38 +0100 (CET) Subject: [records] equality on float / double components In-Reply-To: <4A4F6845-0222-4F84-B8D8-D64E04B5B4ED@oracle.com> References: <9f776a91-8aab-c2d4-0fe2-ffa648c4fef4@oracle.com> <4A4F6845-0222-4F84-B8D8-D64E04B5B4ED@oracle.com> Message-ID: <1437770585.1486515.1512586298372.JavaMail.zimbra@u-pem.fr> +1 R?mi > De: "Paul Sandoz" > ?: "Kevin Bourrillion" > Cc: "amber-spec-experts" > Envoy?: Mercredi 6 D?cembre 2017 19:49:08 > Objet: Re: [records] equality on float / double components >> On 6 Dec 2017, at 10:22, Kevin Bourrillion < [ mailto:kevinb at google.com | >> kevinb at google.com ] > wrote: >> I'd strongly expect this to behave exactly as Float.equals() does. The +0.0/-0.0 >> problem exists but is nothing new. > I concur. Float/Double.equals/compare is also used for float/double[] array > equals and compare. > Paul. >> On Wed, Dec 6, 2017 at 1:07 PM, Brian Goetz < [ mailto:brian.goetz at oracle.com | >> brian.goetz at oracle.com ] > wrote: >>> It's time to play everyone's favorite game show, "What about NaN". >>> If we have a record: >>> record Foo(float f); >>> We would like Object.equals() to be reflexive, symmetric, and transitive. But if >>> we define equals() in the obvious way (delegating to float==), then `new >>> Foo(Float.NaN`) would not be equal to itself. >>> If we delegate instead to `Float.compare(this.f, that.f)`, the NaN problem goes >>> away (though comparison becomes modestly more expensive), but now +0 and -0 are >>> distinguished (== treats them the same.) >> -- >> Kevin Bourrillion | Java Librarian | Google, Inc. | [ mailto:kevinb at google.com | >> kevinb at google.com ] -------------- next part -------------- An HTML attachment was scrubbed... URL: From john.r.rose at oracle.com Wed Dec 6 19:09:35 2017 From: john.r.rose at oracle.com (John Rose) Date: Wed, 6 Dec 2017 11:09:35 -0800 Subject: [records] equality on float / double components In-Reply-To: <1437770585.1486515.1512586298372.JavaMail.zimbra@u-pem.fr> References: <9f776a91-8aab-c2d4-0fe2-ffa648c4fef4@oracle.com> <4A4F6845-0222-4F84-B8D8-D64E04B5B4ED@oracle.com> <1437770585.1486515.1512586298372.JavaMail.zimbra@u-pem.fr> Message-ID: On Dec 6, 2017, at 10:51 AM, Remi Forax wrote: > > +1 +1; glad that everybody's on the same page here -------------- next part -------------- An HTML attachment was scrubbed... URL: From john.r.rose at oracle.com Wed Dec 6 19:18:30 2017 From: john.r.rose at oracle.com (John Rose) Date: Wed, 6 Dec 2017 11:18:30 -0800 Subject: [records] equality on float / double components In-Reply-To: <1437770585.1486515.1512586298372.JavaMail.zimbra@u-pem.fr> References: <9f776a91-8aab-c2d4-0fe2-ffa648c4fef4@oracle.com> <4A4F6845-0222-4F84-B8D8-D64E04B5B4ED@oracle.com> <1437770585.1486515.1512586298372.JavaMail.zimbra@u-pem.fr> Message-ID: <8A8C22AC-A404-410C-84A0-59113487BF82@oracle.com> On Dec 6, 2017, at 10:51 AM, Remi Forax > wrote: > > +1 +1; glad that everybody's on the same page here -------------- next part -------------- An HTML attachment was scrubbed... URL: From kevinb at google.com Wed Dec 6 19:39:46 2017 From: kevinb at google.com (Kevin Bourrillion) Date: Wed, 6 Dec 2017 14:39:46 -0500 Subject: compareTo and deriving In-Reply-To: <70794241-5999-fec3-bf04-8276948592ed@oracle.com> References: <1785948989.404045.1512394463315.JavaMail.zimbra@u-pem.fr> <95c85f87-0624-0531-e490-dd28b301bbbb@oracle.com> <2014433279.467778.1512400378168.JavaMail.zimbra@u-pem.fr> <1381439255.655271.1512461554364.JavaMail.zimbra@u-pem.fr> <70794241-5999-fec3-bf04-8276948592ed@oracle.com> Message-ID: For what it's worth the AutoValue FAQ says this: How do I also generate compareTo? AutoValue intentionally does not provide this feature. It is better for you to roll your own comparison logic using the new methods added to Comparator in Java 8, or ComparisonChain from Guava . Since these mechanisms are easy to use, require very little code, and give you the flexibility you need, there's really no way for AutoValue to improve on them! (The "flexibility you need" having actually just been described in Brian's last message.) I think it holds equally for records in the language. On Tue, Dec 5, 2017 at 10:17 AM, Brian Goetz wrote: > At some level, this goes to the heart of "why should records, and not > tuples, be our 'plain data' abstraction." Adding behavior to a record, > **where that behavior is derived strictly from the state**, is easy and > natural. > > You can look at the "but I have to write compareTo" issue as glass 90% > full or 10% empty; a record starts life with a sensible ctor, dtor, > accessors, equals, hashCode, and toString. So adding compareTo is pretty > easy. Note also that there's no way we can reasonably derive compareTo > from the state description, since (a) not all state fields are going to be > relevant for ordering comparison and (b) the order in which they are > declared may not be the natural comparison order. So somewhere in the > program the user is going to have to write down that "a Person is compared > by (lastName, firstName)", somehow. > > We could of course invent all sorts of shorthands, but I think the > return-on-complexity there is limited, as the natural way to say this is > pretty straightforward: > > record Person(String first, String last, String age) > implements Comparable { > > private static final Comparator cc > = Comparators.comparing(Person::last).thenComparing(Person::fi > rst); > > public int compareTo(Person other) { > return cc.compareTo(other); > } > } > > But, let's not get distracted by Billy, we care about semantics. Where are > the pitfalls you see of this approach where it might run afoul of the > "consistency between equals and compareTo is recommended" dictum? > > > > On 12/5/2017 3:12 AM, forax at univ-mlv.fr wrote: > >> So let restart by trying to untangle things. >> >> First question, should we only support Object methods (toString, equals, >> hashCode) or more, again here, two sub questions, >> it can be only (toString, equals, hashCode) in the first release and more >> in a subsequent release. >> Why more, the question is where to put the bar, if we do only toString, >> equals, hashCode, people will ask for compareTo (i don't think there is >> another method than compareTo but i may be wrong). The rational to >> introduce compareTo is that the semantics of compareTo is linked to the >> semantics of equals which is generated, so asking someone to write >> compareTo given that he doesn't have to write equals is clunky. There are >> several reason to not introduce compareTo. toString, equals, hashCode cover >> most of the case and we have to stop somewhere, people tend to use >> compareTo at a place where they should use a Comparator, i.e. the order >> defined by compareTo is not a 'natural' order. >> >> Second question, how to specify the mapping between toString, equals, >> hashCode and their implementation ? Should we have a way to extends the >> mapping if we want to introduce compareTo later without having to change >> the JLS. Again, sub-questions, Should this mapping being explicit like in >> Haskell with deriving or should it be implicit, there is no real need to >> specify a mapping for the method for j.l.Object because those method >> declaration are already present, and if the record implements Comparable it >> means that we have to provide the implementation of compareTo. >> >> regards, >> R?mi >> >> ----- Mail original ----- >> >>> De: forax at univ-mlv.fr >>> ?: "Brian Goetz" >>> Cc: "amber-spec-experts" >>> Envoy?: Lundi 4 D?cembre 2017 16:12:58 >>> Objet: Re: compareTo and deriving >>> ----- Mail original ----- >>> >>>> De: "Brian Goetz" >>>> ?: "Remi Forax" , "amber-spec-experts" >>>> >>>> Envoy?: Lundi 4 D?cembre 2017 15:54:13 >>>> Objet: Re: compareTo and deriving >>>> As you might remember from the EG meeting, we have toyed with a notion >>>> of "method builders" which would be a more declarative way to declare >>>> template-izable methods. The intent all along was that, once we have >>>> such a mechanism, records would just be a consumer of such a mechanism. >>>> >>>> That said, such a mechanism is bigger both from a technical perspective >>>> and a bikeshed perspective. 90% of the current OO-ceremony-pain is in >>>> ctors, Object methods, and accessors. So my preference would be to nail >>>> down the semantics of data classes first, and then explore whether it >>>> makes sense to surface a more general mechanism. >>>> >>> I'm trying to propose a middle ground (not too far from only support >>> Object >>> methods) to allow to not have too much couple between the JLS and the >>> metafactory methods. >>> >>> One advantage i see to have compareTo generated is that most >>> implementation i >>> see of the compareTo are broken either because of the primitive >>> overflowing or >>> compareTo and equals not having compatible semantics. One problem we >>> will have >>> if we let people to write compareTo is that it will be harder to get it >>> right >>> because equals will be generated while compareTo will be written manually >>> without seeing the code of equals. >>> >>> R?mi >>> >>> >>>> On 12/4/2017 8:34 AM, Remi Forax wrote: >>>> >>>>> Ok, >>>>> record/data class currently provides getters and >>>>> toString/equals/hashCode, what >>>>> if i want to add a compareTo. >>>>> >>>>> In Haskell, it's easy, one can use 'deriving', in Java, it can be >>>>> written that >>>>> way, >>>>> record Point(int x, int y) implements Comparable >>>>> deriving equals, hashCode, toString, compareTo; >>>>> >>>>> For the compiler, the method exists in the supertypes, so the >>>>> signature of the >>>>> method can be computed from the super types, >>>>> after each generated method use an invokedynamic with the same meta >>>>> protocol (a >>>>> list of getters), >>>>> so if by convention "equals", "hashCode", "toString", "compareTo" are >>>>> the names >>>>> of some bootstrap methods in a class DerivingMetaFactory, >>>>> it can be trivial for a JDK developer to add a new bootstrap method >>>>> without >>>>> having to change the JLS. >>>>> >>>>> So my point is, that we can specify once how the compiler should >>>>> generate codes >>>>> for any generated methods in an extensible way. >>>>> >>>>> regards, >>>>> R?mi >>>>> >>>> > -- Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb at google.com -------------- next part -------------- An HTML attachment was scrubbed... URL: From kevinb at google.com Wed Dec 6 19:46:58 2017 From: kevinb at google.com (Kevin Bourrillion) Date: Wed, 6 Dec 2017 14:46:58 -0500 Subject: Named parameters [was: Re: Reader mail bag] In-Reply-To: <1728761728.2390596.1512084760387.JavaMail.zimbra@u-pem.fr> References: <542972726.2360777.1512073261898.JavaMail.zimbra@u-pem.fr> <3D92F35D-0CCB-437B-8CD5-C824219971D1@oracle.com> <1728761728.2390596.1512084760387.JavaMail.zimbra@u-pem.fr> Message-ID: Yes, sometimes you can't choose a good default, or you hurt users by changing your default. But that's not the common case. The common case is that default values are extremely useful, letting an API designer *allow* a customization without *requiring* it. If you look at the preponderance of (a) telescoping method/constructor signatures and (b) builders, it's quite plain that this is a frequent need. In fact, I'd say that the *best* thing about supporting named parameters is that it allows for parameters to be optional. Google's codebase is about 743% made up of Builders*, and our interest in a named/optional parameters feature is directly proportional to how many of these builders we think it could eliminate. :-) *figure may be exaggerated slightly On Thu, Nov 30, 2017 at 6:32 PM, wrote: > I will just anwser on the default argument part. > > Java already has a way to specify default argument using several overloads > with a different numbers of argument, it's curbersome but it should not be > too easy to specify default values. > I believe that default argument are evil for two main reasons. > > It's like playing God, it's not the user of an API that specifies the > default value, it's the creator of the API, so it's the creator the API > saying to the world, i know how you will using my API, here are the default > value you want. I tink we should be more humble when developing APIs and > not pretend that we know what the good default are for all our users. > > Defaults are also part of the contract but people tends to think loosely > about that part, defaults are implicit, so a part of the configuration of > an object or a method in not visible at callsite, this leads to > misunderstanding between what a user of a method think the method does and > what the method really do. > > So i'm all for named parameters if it makes a method call easier to > understand, if it makes method call harder to understand by introducing > implicit values, i think i prefer the statu quo. > > R?mi > > ----- Mail original ----- > > De: "Guy Steele" > > ?: "Remi Forax" > > Cc: "Brian Goetz" , "amber-spec-experts" < > amber-spec-experts at openjdk.java.net> > > Envoy?: Jeudi 30 Novembre 2017 21:56:27 > > Objet: Named parameters [was: Re: Reader mail bag] > > > Thanks, Remi, this is an excellent start! And it may be where we want > to end > > up. > > > > But when the time comes that we dig into a serious discussion of putting > named > > method parameters into Java, I would like to see a broader exploration > of the > > design space before we settle on a specific design. > > > > I?ve seen a lot of other designs for a lot of other languages, each with > pros > > and cons. > > > > There are at least four more-or-less orthogonal properties a method > parameter > > can have: > > > > (1) May it be specified by name (rather than by position) at the call > site? > > If so, can it be specified _either_ by name or by position? > > > > (2) May the corresponding actual argument be omitted at the call site? > > If so, what happens? > > System-specified default value (such as zero or null) is > supplied. > > Programmer-specified default value is supplied. > > Value is a compile-time constant. > > Value is recomputed at call time. > > Can this computation depend on other > argument values (such as those to the > > left)? > > No value is supplied. > > A separate mechanism allows inquiry as to whether > an actual argument was > > provided. > > The parameter type is actually an option type > (choice; if no value is supplied, > > you get an empty value). > > > > (3) May the corresponding actual argument be duplicated at the call site? > > (This may make little sense for Java, but is used extensively in > Common Lisp, > > where name-value lists may be built dynamically and then fed to > `apply`; > > allowing duplications makes it easy to override a default by just > sticking a > > new name-value pair onto the front of a list.) > > > > (4) May the actual arguments be permuted at the call site?that is, > appear in an > > order other than the order in which they are declared in the method > > declaration? > > (Typically the answer is ?no? for positional parameters, but may > be ?yes? or > > ?no? for named parameters.) > > (If a call contains both positional and named arguments, one can > ask whether the > > named arguments may be mixed in among the positional ones [yech!] > or must be > > kept separate, such as always appearing to the right of the > positional > > arguments.) > > > > For each of the preceding four questions, there are these meta-questions: > > Is the answer to the question the same for all parameters > whatsoever? > > Is the answer to the question the same for all parameters of the > same kind (such > > as named or positional)? > > Is the answer to the question the same for all parameters in a > single method > > declaration? > > Is the answer to the question the same for all parameters of the > same kind (such > > as named or positional) in a single method declaration? > > > > In addition, there is the question of exactly what combinations of > positional, > > optional positional, named, and/or optional named parameters may be used > within > > a single method declaration. > > And there is the question of what combinations of combinations may > appear within > > an overload set. > > > > Many of these questions are in fact answered by specific choices in the > proposal > > below. I?m just looking to seeing (eventually) a thorough discussion of > the > > rationale for each choice. I provide this list of questions as one > possible > > starting point for that discussion. > > > > ?Guy > > > >> On Nov 30, 2017, at 3:21 PM, Remi Forax wrote: > >> > >> My note on named parameters: > >> Supporting real named parameters that works with overriding and backward > >> compatibility is hard, but i believe there is a sweet spot. > >> > >> - a method can declare to support named parameters by using a new > modifier > >> 'named'. > >> so - all parameters are named or none are, you can not mix positional > and named > >> parameters like in Ruby. > >> - a method is either support positional parameters or named > parameters but not > >> both at the same time. > >> if there are several overloads, you can only have one named overload > with the > >> same number of parameters, > >> which means that counting the number of parameters is enough to know > which > >> method can be called. > >> a varargs method can not be named. > >> > >> - when overriding a method, a named method can not override a non named > method. > >> overriding a named method with a non named method is allowed to ease > the > >> transition but emit a warning. > >> a named method that overrides another named method need to have the > same > >> parameters with the same name at the same position. > >> > >> - at call site, a named method has to be called using the syntax "name: > >> argument" for each arguments > >> by example: > >> ThreadGroup main = ... > >> new ThreadGroup(parent: main, name: "my group"); > >> if a named method is called with positional arguments, the compiler > emit a > >> warning > >> > >> - at compile time, > >> a declaration site, all parameter named are stored in the Parameter > attribute. > >> a callsite, the compile insert an invokedynamic to the > NamedParameterMetaFactory > >> with a method handle ref on the declared named method and the names > of all > >> arguments in the order of the call. > >> a runtime, the NamedParameterMetaFactory verifies that the number of > named > >> argument is the same and are a permutation of the declared parameters > that are > >> retrieved by cracking the method handle ref as a MethodHandleInfo and > calling > >> getParameters on the corresponding Constructor/Method, once the > permutation is > >> calculated, the NamedParameterMetaFactory returns a ConstantCallSite > on the > >> constant method handle (the method handle ref) permuted using > >> MethodHandles.permuteArguments. > >> > >> To summarize, this let us use named parameters if the API designer want > to allow > >> that, > >> there is a path for upgrading a method that uses positional parameters > to use > >> named parameters, but not in the other way (like varargs), > >> not supporting permutations between overridden methods make thing far > simpler > >> that they are otherwise. > >> > >> cheers, > >> R?mi > >> > >> ----- Mail original ----- > >>> De: "Brian Goetz" > >>> ?: "amber-spec-experts" > >>> Envoy?: Jeudi 30 Novembre 2017 19:29:41 > >>> Objet: Reader mail bag > >> > >>> We've gotten two submissions to the amber-spec-comments list. > >>> > >>> 1. "Forcing a pattern match", at: > >>> http://mail.openjdk.java.net/pipermail/amber-spec-comments/ > 2017-November/000000.html > >>> > >>> 2. "Named parameters in data classes", at: > >>> http://mail.openjdk.java.net/pipermail/amber-spec-comments/ > 2017-November/000003.html > >>> > >>> > >>> I think the first asks for an alternate form of the "matches" operator > >>> which would fail with a CCE, rather than evaluating to false, if the > >>> match fails. This would be essentially saying, "This should match; if > >>> it doesn't, that's an error." I think that's what's being asked, but > >>> then he talks about an "unnecessary instanceof check", which makes me > >>> wonder whether this is really just about optimization. > >>> > >>> To be clear, the instanceof check is neither expensive nor unnecessary. > >>> (Though we can optimize these away when we know the static type of the > >>> target; if x is a Foo, then "x matches Foo f" can be statically > >>> strength-reduced to "x != null".) > >>> > >>> Note that the "if member.getKind() == VARIABLE" is merely a manual > >>> optimization; you could easily leave that out and just match against > >>> VariableTree. What we'd rather focus on is how to get to: > >>> > >>> switch (member) { > >>> case BlockTree bt: ... > >>> case VariableTree vt: ... > >>> } > >>> > >>> while allowing the pattern to capture the quicker pre-test (kind == > >>> BLOCK) and maintain the performance without making the user worry about > >>> this. We have some ideas here, but I don't think this "forcing" idea > >>> really offers a lot. > >>> > >>> > >>> The second (named parameters) was a question I was expecting. > >>> > >>> I agree that being able to invoke constructors and methods by name can > >>> sometimes result in easier-to-read code, especially when some > parameters > >>> have a sensible default value. (This is also a more complicated > feature > >>> than anyone gives it credit for, so it's not the "gimme" it is often > >>> assumed to be.) > >>> > >>> However, data classes is not the place to cram in this feature; this > >>> should be a feature that stands on its own, and applies to all classes, > >>> data- or not. One of the design goals for data classes is that a data > >>> class should be essentially a "macro" for a class you could write by > >>> hand; this allows easy migration from existing classes to data classes > >>> (if they meet the requirements) and from data classes to full classes > if > >>> they expand to no longer fit the data class profile. The more that > >>> *uses* of data classes or their members are different from the > >>> corresponding use of regular classes, the more difficult this migration > >>> becomes. (This is not unlike the design mandate with default methods; > >>> from the client perspective, they're just ordinary virtual methods, and > >>> you can't tell whether the method was implemented directly or inherited > >>> -- they're just methods.) > >>> > >>> So, while named parameters are a reasonable feature to explore, trying > >>> to staple them onto data classes would be a mistake. They are their > own > > >> feature. We're open to exploring it, but we've got our plate full > for now. > -- Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb at google.com -------------- next part -------------- An HTML attachment was scrubbed... URL: From kevinb at google.com Wed Dec 6 19:55:25 2017 From: kevinb at google.com (Kevin Bourrillion) Date: Wed, 6 Dec 2017 14:55:25 -0500 Subject: Guards -- not just for switch! In-Reply-To: References: <84528b93-96c1-a3ea-9cbe-84f3f955e340@oracle.com> Message-ID: Sorry I'm on a thread necromancing kick. I think this feature has value. Anything that encourages more aggressive precondition checking is probably good. I agree that `requires` would be a lot better than `where` for this. I also feel that if we *don't* do something like this, then the design for records has a serious problem, in that there is a huge cost involved to adding a bit of validation to a record that didn't have any before. I think that *must* be addressed, because we need *more* integrity checking in the world, not less. One other way to address it in records is with a @PostConstruct method (where the fields were already set) instead of a call-through constructor, but this `requires` seems more elegant and Java-ey. (@PostConstruct is part of JavaEE because it *feels* like an EE kind of thing.) I will note it's more than a bit weird that code is getting executed from what appears to be the signature of the method/class. Yes, it's like `final int foo = executeWhateverHere()` ... but still feels quite weirder. On Wed, Nov 29, 2017 at 7:24 PM, Tagir Valeev wrote: > > public Range(int lo, int hi) > where (lo <= hi) { > this.lo = lo; > this.hi = hi; > } > > Probably it's too early to argue about syntax, but now it's too similar to > the 'while' loop. The 'where' keyword really looks like 'while' (same > length, prefix and suffix) and just like the 'while' loop it's followed by > parenthesized boolean expression and code block. I bet this would become a > source of confusion when reading the code. The 'requires' keyword, as Remi > suggests, sounds much better. > > With best regards, > Tagir Valeev > -- Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb at google.com -------------- next part -------------- An HTML attachment was scrubbed... URL: From gavin.bierman at oracle.com Thu Dec 7 15:49:00 2017 From: gavin.bierman at oracle.com (Gavin Bierman) Date: Thu, 7 Dec 2017 15:49:00 +0000 Subject: More on patterns, generics, null and primitives Message-ID: Below are some more refined thoughts about patterns, generics, null and primitives. Thank you for your feedback so far. Comments welcome! Gavin ---- Patterns: Types, primitives and null ==================================== In this note, we address some of the issues raised in earlier posts concerning pattern matching. To make matters concrete, let us define a simple grammar of patterns: p ::= l //literal (primitive type constant expression) null //null pattern R x //Reference type test pattern V x //Value type test pattern D(p_1, ..., p_n) //Deconstructor pattern (D is a *data class*) var x //Type inference For simplicity, we have only considered primitive type constant expression literals, but we can easily extend what follows to cover string constant expressions and Enum names. For generality we have included simple deconstruction patterns, which will most likely appear first with data classes - a feature we have proposed elsewhere. Type checking basics ==================== To type check the expression e matches R x where R is a reference type, we check that the type of the expression e is cast convertible to R. This is captured by the following typing rule: e : T T cast-convertible R ------------------------------ e matches R x : bool For value types, we propose a simpler rule. When type checking an expression e matches V x where V is a value type, we shall simply check that the type of e is either the value type V or the boxed type of V. Again we can write this more formally as follows. e : V or e : box(V) ----------------------- e matches V x : bool (We have assumed a function, box, that returns the boxed reference type of a given value type, e.g. box(int)=Integer.) Type checking literal patterns is similar. Thus given an expression e matches l where l is a primitive type constant expression of type V, we check that e has exactly the type V. (We could also permit e to have the boxed type of V, but we disallow this for simplicity.) e : V l : V ------------------ e matches l : bool [A small corner case with this rule is where the primitive type constant expression l is a numeric literal without a suffix, e.g. 1. In this case we would treat l as a poly expression and use the type of the expression e as a target type.] Given an expression e matches null we check that e has some reference type R. e : R --------------------- e matches null : bool Generics ======== Currently instanceof is restricted so that the type must be reifiable. In other words the following is not allowed. ArrayList al = ... if (al instanceof List) { // Error ... } We propose to lift this restriction for pattern matching using type test patterns. In other words, a reference type test pattern match is well typed if the cast conversion is not unchecked. If the cast conversion is unchecked then type checking fails. In what follows we will assume that all pattern matching code has type checked correctly. Nulls ===== To recall, the issue with nulls and pattern matching came from Java?s current slightly inconsistent treatment of null. Currently x instanceof Foo returns false where x is the null value. Clearly, we would want x matches Foo f to do the same. However, when we come to support deconstruction patterns, we would expect Foo(null) to match a pattern Foo(var x). Moreover, we intend the pattern var x to mean the same as Bar x where the type has been inferred to be Bar. So we appear to have conflicting requirements! A further complication is that the switch statement currently throws an exception when the switch expression is the null value. To recap: Foo f = null; //Foo has a String field f matches Foo x //would like to return false as for instanceof switch (f) { case Foo: ... } //throws f = new Foo(null); f matches Foo(String x) //would like to return true and bind x to null f matches Foo(var x) //behave identically to above We need to reconcile the current semantics and find a generalisation that matches (sic) the above behaviour for patterns. First, we shall generalise the switch statement in the following way. We propose that it no longer throws an exception when the switch expression is the null value. However, we propose that all switches have an implicit case null: throw new NullPointerException(); clause, unless there is an explicit case null pattern label in the switch body. In this way, all existing switch statements will continue to mean the same thing, but users of the enhanced switch can now program a case to handle the null value case. We had previously proposed dealing with null by considering the type of the pattern test to determine if it was a type-restatement pattern or not. Whilst this works, it could cause some subtle action-at-a-distance effects that we found discomforting. Another possibility had been to classify patterns as top-level and non-top-level and assign them different semantics. This was felt to be too subtle. We think we now have a solution that avoids this partition of patterns, leading to a simpler and clearer system. The key to this solution is to separate the semantics of a pattern from the semantics of the construct that uses the pattern. tl;dr: The semantics of pattern matching is defined to match null values against type test patterns. The semantics of constructs that use pattern matching is layered over this definition in order to capture the existing behaviours with respect to null values at the top level. Given a pattern p, and a value e of type T, we can define the semantics of this pattern with a function, match, that returns a boolean indicating whether the pattern matches the value or not. (Recall that we assume that type checking has already succeeded.) match(l, e:V) =def e = l match(l, e:R) =def e <> null /\ unbox(e) = l match(null, e:V) =def false // n/a by type-checking match(null, e:R) =def e = null match(R t, e:V) =def false // n/a by type-checking match(R t, e:R') =def e = null \/ e instanceof R match(V t, e:V') =def true // by type-checking, V = V' match(V t, e:R) =def e <> null // by type-checking, R = box(V) match(D(pa_1, ..., pa_n), e:V) =def false match(D(pa_1, ..., pa_n), e:R) =def e instanceof D /\ match(pa_1, e.x_1:T_1) /\ ... /\ match(pa_n, e.x_n:T_n) where data class D(T_1 x_1, ..., T_n x_n); This definition should be intuitive, but let us consider the first two cases that deal with matching a value e against a literal pattern l. If e has a value type then the pattern match is just a simple check for equality between the value and the literal. If e has a reference type then we first check if it has the null value. If so we return false, otherwise we unbox the value and recursively check this against the pattern. The important case is where we have a type test pattern for a reference type R and a value of a reference type. In this case we check if the value has the null value. If so we return true, otherwise we check whether the value is an instance of the type R. In other words, the inherent semantics of pattern matching returns true when matching a null value against a reference type test R x. Note there is no case for the var pattern as the compiler will have replaced it with an explicit type test pattern as a result of type inference. The final step is to use this to define the semantics of the two constructs that use pattern matching, i.e. matches and switch. This can be summarised using the following table (where e has static type T). | e = null | e <> null ------------------------------------------------- | | e matches P | false | match(P, e:T) | | | | switch (e) { | | case null: s | s | } | | | | | | switch (e) { | | // P not null | NPE | s if match(P,e:T) case P : s | | } | | | | Below is a number of examples demonstrating these semantics in action. Object o = null; o matches String s // false o matches Box(var x) // false new Box(o) matches Box b // true new Box(o) matches Box(null) // true new Box(o) matches Box(Object o) // true new Box(o) matches Box(var o) // true (o inferred to have // type Object) switch (o) { case String s: } // NPE switch (o) { case null: System.out.println("hello"); } // Prints hello o=(Object)"Hello"; switch (o) { case String s: System.out.println(s); } // Prints Hello switch (o) { case null: break; case String s: System.out.println(s); } // Prints Hello o=null; switch (new Box(o)) { case Box b: System.out.println("Box"); } // Prints Box switch (new Box(o)) { case Box(null): System.out.println("A"); break; case Box(var x): System.out.println("B"); } // Prints A switch (new Box(o)) { case Box(var x): System.out.println(x==null); } // Prints true From brian.goetz at oracle.com Thu Dec 7 16:36:43 2017 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 7 Dec 2017 11:36:43 -0500 Subject: Switch translation, part 1 Message-ID: <6cf5957c-13e4-6a1b-0426-132799eadcf6@oracle.com> This doc covers some groundwork on rationalizing existing translation of switch as we move towards pattern-enabled switches. # Switch Translation, Part 1 #### Maurizio Cimadamore and Brian Goetz #### December 2017 This document examines the current translation of `switch` constructs by `javac`, and proposes a more general translation for switching on primitives, boxes, strings, and enums, with the goals of: ?- Unify the treatment of `switch` variants, simplifying the compiler implementation and reducing the static footprint of generated code; ?- Move responsibility for target classification from compile time to run time, allowing us to more freely update the logic without updating the compiler; ?- Lay the groundwork for patterns in `switch`. Part 2 of this document will focus on the challenges of translating pattern `switch`. ## Current translation Switches on `int` (and the smaller integer primitives) are translated in one of two ways.? If the labels are relatively dense, we translate an `int` switch to a `tableswitch`; if they are sparse, we translate to a `lookupswitch`.? The current heuristic appears to be that we use a `tableswitch` if it results in a smaller bytecode than a `lookupswitch` (which uses twice as many bytes per entry), which is a reasonable heuristic. #### Switches on boxes Switches on primitive boxes are currently implemented as if they were primitive switches, unconditionally unboxing the target before entry (possibly throwing NPE). #### Switches on strings Switches on strings are implemented as a two-step process, exploiting the fact that strings cache their `hashCode()` and that hash codes are reasonably spread out. Given a switch on strings like the one below: ??? switch (s) { ??????? case "Hello": ... ? ?? ?? case "World": ... ??????? default: ... ??? } The compiler desugar this into two separate switches, where the first switch maps the input strings into a range of numbers [0..1], as shown below, which can then be used in a subsequent plain switch on ints.? The generated code unconditionally calls `hashCode()`, again possibly throwing NPE. ??? int index=-1; ??? switch (s.hashCode()) { ? ?? ?? case 12345: if (!s.equals("Hello")) break; index = 1; break; ? ?? ?? case 6789: if (!s.equals("World")) break; index = 0; break; ? ?? ?? default: index = -1; ??? } ??? switch (index) { ? ?? ?? case 0: ... ??????? case 1: ... ? ?? ?? default: ... ??? } If there are hash collisions between the strings, the first switch must try all possible matching strings. #### Switches on enums Switches on `enum` constants exploit the fact that enums have (usually dense) integral ordinal values.? Unfortunately, because an ordinal value can change between compilation time and runtime, we cannot rely on this mapping directly, but instead need to do an extra layer of mapping.? Given a switch like: ??? switch(color) { ??????? case RED: ... ??????? case GREEN: ... ??? } The compiler numbers the cases starting a 1 (as with string switch), and creates a synthetic class that maps the runtime values of the enum ordinals to the statically numbered cases: ??? class Outer$0 { ??????? synthetic final int[] $EnumMap$Color = new int[Color.values().length]; ? ?? ?? static { ??? ? ? ? ? try { $EnumMap$Color[RED.ordinal()] = 1; } catch (NoSuchFieldError ex) {} ? ?? ? ?? ? try { $EnumMap$Color[GREEN.ordinal()] = 2; } catch (NoSuchFieldError ex) {} ??????? } ??? } Then, the switch is translated as follows: ??? switch(Outer$0.$EnumMap$Color[color.ordinal()]) { ??????? case 1: stmt1; ? ?? ?? case 2: stmt2 ??? } In other words, we construct an array whose size is the cardinality of the enum, and then the element at position *i* of such array will contain the case index corresponding to the enum constant with whose ordinal is *i*. ## A more general scheme The handling of strings and enums give us a hint of how to create a more regular scheme; for `switch` targets more complex than `int`, we lower the `switch` to an `int` switch with consecutive `case` labels, and use a separate process to map the target into the range of synthetic case labels. Now that we have `invokedynamic` in our toolbox, we can reduce all of the non-`int` cases to a single form, where we number the cases with consecutive integers, and perform case selection via an `invokedynamic`-based classifier function, whose static argument list receives a description of the actual targets, and which returns an `int` identifying what `case` to select. This approach has several advantages: ?- Reduced compiler complexity -- all switches follow a common pattern; ?- Reduced static code size; ?- The classification function can select from a wide range of strategies (linear search, binary search, building a `HashMap`, constructing a perfect hash function, etc), which can vary over time or from situation to situation; ?- We are free to improve the strategy or select an alternate strategy (say, to optimize for startup time) without having to recompile the code; ?- Hopefully at least, if not more, JIT-friendly than the existing translation. We can also use this approach in preference to `lookupswitch` for non-dense `int` switches, as well as use it to extend `switch` to handle `long`, `float`, and `double` targets (which were surely excluded in part because the JVM didn't provide a convenient translation target for these types.) #### Bootstrap design When designing the `invokedynamic` bootstraps to support this translation, we face the classic lumping-vs-splitting decision. For now, we'll bias towards splitting.? In the following example, `BOOTSTRAP_PREAMBLE` indicates the usual leading arguments for an indy bootstrap.? We assume the compiler has numbered the case values densely from 0..N, and the bootstrap will return [0,n) for success, or N for "no match". A strawman design might be: ??? // Numeric switches for P, accepts invocation as P -> I or Box(P) -> I ??? CallSite intSwitch(BOOTSTRAP_PREAMBLE, int... caseValues) ??? // Switch for String, invocation descriptor is String -> I ??? CallSite stringSwitch(BOOTSTRAP_PREAMBLE, String... caseValues) ??? // Switch for Enum, invocation descriptor is E -> I ??? CallSite enumSwitch(BOOTSTRAP_PREAMBLE, Class>> clazz, ??????????????????????? String... caseNames) It might be possible to encode all of these into a single bootstrap, but given that the compiler already treats each type slightly differently, it seems there is little value in this sort of lumping for non-pattern switches. The `enumSwitch` bootstrap as proposed uses `String` values to describe the enum constants, rather than encoding the enum constants directly via condy.? This allows us to be more robust to enums disappearing after compilation. This strategy is also dependent on having broken the limitation on 253 bootstrap arguments in indy/condy. #### Extending to other primitive types This approach extends naturally to other primitive types (long, double, float), by the addition of some more bootstraps (which need to deal with the additional complexities of infinity, NaN, etc): ??? CallSite longSwitch(BOOTSTRAP_PREAMBLE, long... caseValues) ??? CallSite floatSwitch(BOOTSTRAP_PREAMBLE, float... caseValues) ??? CallSite doubleSwitch(BOOTSTRAP_PREAMBLE, double... caseValues) #### Extending to null The scheme as proposed above does not explicitly handle nulls, which is a feature we'd like to have in `switch`.? There are a few ways we could add null handling into the API: ?- Split entry points into null-friendly or null-hostile switches; ?- Find a way to encode nulls in the array of case values (which can be done with condy); ?- Always treat null as a possible input and a distinguished output, and have the compiler ensure the switch can handle this distinguished output. The last strategy is appealing and straightforward; assign a sentinel value (-1) to `null`, and always return this sentinel when the input is null.? The compiler ensures that some case handles `null`, and if no case handles `null` then it inserts an implicit ??? case -1: throw new NullPointerException(); into the generated code. #### General example If we have a string switch: ??? switch (x) { ??????? case "Foo": m(); break; ??????? case "Bar": n(); // fall through ??????? case "Baz": r(); break; ??????? default: p(); ??? } we translate into: ??? int t = indy[bsm=stringSwitch["Foo", "Bar", "Baz"]](x) ??? switch (t) { ??????? case -1: throw new NullPointerException();? // implicit null case ??????? case 0: m(); break; ??????? case 1: n(); // fall through ??????? case 2: r(); break; ??????? case 3: p();??????????????????????????????? // default case ??? } All switches, with the exception of `int` switches (and maybe not even non-dense `int` switches), follow this exact pattern.? If the target type is not a reference type, the `null` case is not needed. ## Patterns in narrow-target switches When we add patterns to the language, we may encounter switches whose targets are tightly typed (e.g., `String` or `int`) but still use some patterns in their expression.? For switches whose target type is a primitive, primitive box, `String`, or `enum`, we'd like to use the optimized translation strategy outlined here, but the following kinds of patterns might still show up in a switch on, say, `Integer`: ??? case var x: ??? case _: ??? case Integer x: ??? case Integer(var x): The first three (if supported at all) can be translated away by the source compiler, as they are semantically equivalent to `default`.? If any nontrivial patterns are present (including deconstruction patterns), we will need to translate as a pattern switch scheme -- details coming in Part 2.? (While the language may not distinguish between "legacy" and "pattern" switches -- in that all switches are pattern switches -- we'd like to avoid giving up obvious optimizations if we can.) ## Looking ahead -- patterns A key motivation for reexamining switch translation is the impending arrival of patterns in switch.? We expect switch translation for the pattern case to follow a similar structure -- lower to an `int` switch and use an indy-based classifier to select an index.? However, there are a few additional complexities.? One is that pattern cases may have guards, which means we need to be able to re-enter the bootstrap with an indication to "continue matching from case N", in the event of a failed guard. Translating pattern switches is more complicated because there are more options for how to divide the work between the statically generated code and the switch classifier, and different choices have different performance side-effects (are binding variables "boxed" into a tuple to be returned, or do they need to be redundantly calculated).? These will be explored in Part 2. -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Thu Dec 7 22:33:36 2017 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 7 Dec 2017 17:33:36 -0500 Subject: New JEP: Switch Expressions for the Java Language Message-ID: <1932fb57-fda7-ac1d-57ec-2d062b56766e@oracle.com> We've separated out a package of standalone improvements to `switch` (switch expressions, case null, and case alternation) into their own JEP: ??? https://bugs.openjdk.java.net/browse/JDK-8192963 From forax at univ-mlv.fr Thu Dec 7 23:02:03 2017 From: forax at univ-mlv.fr (Remi Forax) Date: Fri, 8 Dec 2017 00:02:03 +0100 (CET) Subject: New JEP: Switch Expressions for the Java Language In-Reply-To: <1932fb57-fda7-ac1d-57ec-2d062b56766e@oracle.com> References: <1932fb57-fda7-ac1d-57ec-2d062b56766e@oracle.com> Message-ID: <407136193.2049071.1512687723295.JavaMail.zimbra@u-pem.fr> Hi Brian, correct me of i'm wrong but there is no discussion about accessing to a local variable in the body of an expression, void m(int x, int y) { return switch(x) { case 0 -> y; // is it allowed, how is it translated ? default -> 0; }; } regards, R?mi ----- Mail original ----- > De: "Brian Goetz" > ?: "amber-spec-experts" > Envoy?: Jeudi 7 D?cembre 2017 23:33:36 > Objet: New JEP: Switch Expressions for the Java Language > We've separated out a package of standalone improvements to `switch` > (switch expressions, case null, and case alternation) into their own JEP: > > ??? https://bugs.openjdk.java.net/browse/JDK-8192963 From cushon at google.com Thu Dec 7 23:28:44 2017 From: cushon at google.com (Liam Miller-Cushon) Date: Thu, 7 Dec 2017 15:28:44 -0800 Subject: New JEP: Switch Expressions for the Java Language In-Reply-To: <1932fb57-fda7-ac1d-57ec-2d062b56766e@oracle.com> References: <1932fb57-fda7-ac1d-57ec-2d062b56766e@oracle.com> Message-ID: I don't see it mentioned in the JEP, but will the default case of an expression switch be required to be last? (Previously: http://mail.openjdk.java.net/pipermail/amber-spec-observers/2017-November/000134.html ) > in the case of an enum switch that covers all known cases, a default clause can be inserted by the compiler that indicates that the enum definition has changed between compile time and runtime This sounds great, but note that it makes adding values to an enum a potentially source-incompatible change. Pattern switches that handle all values of an enum will need to be updated to handle the new value, or to have an explicit default. In our code base we use static analysis to require statement switches on enums either handle all values or have a default. The issue with source incompatibility and adding values to enums hasn't been a significant problem so far. On Thu, Dec 7, 2017 at 2:33 PM, Brian Goetz wrote: > We've separated out a package of standalone improvements to `switch` > (switch expressions, case null, and case alternation) into their own JEP: > > https://bugs.openjdk.java.net/browse/JDK-8192963 > > > -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Fri Dec 8 14:48:36 2017 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 8 Dec 2017 09:48:36 -0500 Subject: New JEP: Switch Expressions for the Java Language In-Reply-To: <407136193.2049071.1512687723295.JavaMail.zimbra@u-pem.fr> References: <1932fb57-fda7-ac1d-57ec-2d062b56766e@oracle.com> <407136193.2049071.1512687723295.JavaMail.zimbra@u-pem.fr> Message-ID: Clearly accessing locals should be allowed, to some degree -- it would be weird if we could not reference y here.? But it is reasonable to ask, what restrictions do we want to place?? Are the same restrictions as lambdas -- effectively-final only -- too restrictive?? (Certainly such a restriction opens up more options for translation strategies, and is consistent with other -> contexts.) On 12/7/2017 6:02 PM, Remi Forax wrote: > Hi Brian, > correct me of i'm wrong but there is no discussion about accessing to a local variable in the body of an expression, > > void m(int x, int y) { > return switch(x) { > case 0 -> y; // is it allowed, how is it translated ? > default -> 0; > }; > } > > regards, > R?mi > > ----- Mail original ----- >> De: "Brian Goetz" >> ?: "amber-spec-experts" >> Envoy?: Jeudi 7 D?cembre 2017 23:33:36 >> Objet: New JEP: Switch Expressions for the Java Language >> We've separated out a package of standalone improvements to `switch` >> (switch expressions, case null, and case alternation) into their own JEP: >> >> ??? https://bugs.openjdk.java.net/browse/JDK-8192963 From forax at univ-mlv.fr Fri Dec 8 14:59:44 2017 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Fri, 8 Dec 2017 15:59:44 +0100 (CET) Subject: New JEP: Switch Expressions for the Java Language In-Reply-To: References: <1932fb57-fda7-ac1d-57ec-2d062b56766e@oracle.com> <407136193.2049071.1512687723295.JavaMail.zimbra@u-pem.fr> Message-ID: <768893916.2311613.1512745184419.JavaMail.zimbra@u-pem.fr> ----- Mail original ----- > De: "Brian Goetz" > ?: "Remi Forax" > Cc: "amber-spec-experts" > Envoy?: Vendredi 8 D?cembre 2017 15:48:36 > Objet: Re: New JEP: Switch Expressions for the Java Language > Clearly accessing locals should be allowed, to some degree -- it would > be weird if we could not reference y here.? But it is reasonable to ask, > what restrictions do we want to place?? yes, > Are the same restrictions as lambdas -- effectively-final only -- too restrictive?? I believe it's ok to have the same restrictions as lambdas. We still have the classical switch as backup and given that the current proposed syntax uses '->', it will be weird if expression/body after the -> has another set of rules as the lambda ones. > (Certainly such a restriction opens up more options for translation strategies, and is > consistent with other -> contexts.) yes, if the expression switch as the same restrictions as a lambda expression/body, it means we can represent each expression as a static method like with the lambda translation instead of having one blob of code for all expressions, so we can re-organise them, not even make one of them present if the case is never called, etc. R?mi > > On 12/7/2017 6:02 PM, Remi Forax wrote: >> Hi Brian, >> correct me of i'm wrong but there is no discussion about accessing to a local >> variable in the body of an expression, >> >> void m(int x, int y) { >> return switch(x) { >> case 0 -> y; // is it allowed, how is it translated ? >> default -> 0; >> }; >> } >> >> regards, >> R?mi >> >> ----- Mail original ----- >>> De: "Brian Goetz" >>> ?: "amber-spec-experts" >>> Envoy?: Jeudi 7 D?cembre 2017 23:33:36 >>> Objet: New JEP: Switch Expressions for the Java Language >>> We've separated out a package of standalone improvements to `switch` >>> (switch expressions, case null, and case alternation) into their own JEP: >>> > >> ??? https://bugs.openjdk.java.net/browse/JDK-8192963 From brian.goetz at oracle.com Fri Dec 8 15:01:06 2017 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 8 Dec 2017 10:01:06 -0500 Subject: New JEP: Switch Expressions for the Java Language In-Reply-To: References: <1932fb57-fda7-ac1d-57ec-2d062b56766e@oracle.com> Message-ID: On 12/7/2017 6:28 PM, Liam Miller-Cushon wrote: > I don't see it mentioned in the JEP, but will the default case of an > expression switch be required to be last? (Previously: > http://mail.openjdk.java.net/pipermail/amber-spec-observers/2017-November/000134.html) We've not fully worked through these details yet.? Here's my current thinking. We don't want to bifurcate the world into legacy switch and new switch.? Instead, we'd like to generalize switch, and then, where something makes sense for (say) int switch but not for the general case, specify those restrictions in terms of what's in the case labels.? (For example (not saying we will do this), take fallthrough.? If we decided we were going to kill fallthrough in patterns, rather than saying "no fallthrough in pattern switch", we'd say "no falling into patterns of XYZ kind.") The so-called-legacy switches have an interesting property; all their cases, other than default, are mutually exclusive.? (If you match "foo", you can't match "bar".)? This means that ordering is irrelevant (modulo fallthrough.)? OTOH, with more interesting switches, a case can subsume another case, so its important that they be put in the right order (having "case Object" before "case String" would mean the latter is dead, and we treat this as an error.)? In this light, "default" means "case anything else", so if there's even a tiny bit of room for confusion, it should go last. What we do about existing switches is still open.? For consistency[1], we might want to remake the world to say "default goes last."? But, this has a cost (needless code churn, which will be viewed as ceremony), and its not clear the consistency benefit is warranted.? So we might say something like "if any case label in the switch has a defined dominance ordering with any other case label, default must be the last case."? That way old code continues to work, but once you migrate to nontrivial patterns (which involves source changes anyway), you need to put things right.? This seems a good compromise. For expression switch, we can be a little stricter, since there's no existing code.? On the other hand, its good to have the same set of rules for expression and statement switch (all things being equal). So this could go either way. [1] Language-evolution arguments that start with "for consistency, ..." are usually weak. > > in the case of an enum switch that covers all known cases, a default > clause can be inserted by the compiler that indicates that the enum > definition has changed between compile time and runtime > > This sounds great, but note that it makes adding values to an enum a > potentially source-incompatible change. Pattern switches that handle > all values of an enum will need to be updated to handle the new value, > or to have an explicit default. Yes, but that doesn't bother me so much.? Adding a member to an enum that is exposed across compilation unit boundaries should be an unusual case.? If someone has explicitly coded defensively to deal with this, great.? If they haven't, having their switch code break when someone else changes an assumption out from under them is a good thing. Users (rightly) find it irritating that, when they define an enum: ??? enum TrafficLight { RED, YELLOW, GREEN } that they constantly have to deal with "might be some other color." Not all enums have this characteristic, but many do, and I think its reasonable to let users err on the side of this assumption.? They can always code an explicit default if they want. From brian.goetz at oracle.com Sun Dec 10 17:56:51 2017 From: brian.goetz at oracle.com (Brian Goetz) Date: Sun, 10 Dec 2017 12:56:51 -0500 Subject: New JEP: Switch Expressions for the Java Language In-Reply-To: <1932fb57-fda7-ac1d-57ec-2d062b56766e@oracle.com> References: <1932fb57-fda7-ac1d-57ec-2d062b56766e@oracle.com> Message-ID: <6F1D8934-488E-4643-A006-4D1CC57C70E7@oracle.com> Here?s another way of looking at expression switch. This is a translation-centric view, but may be useful both as a mental model and as a specification approach as well. If I have an expression switch: T y = switch (x) { case A -> e; case B -> f; } We could implement by desugaring the switch into the invocation of a synthetic method: T y = m$switch(x, a, b, ...) where a, b, ... are locals mentioned in e and f. And m$switch() is: - replace "case L -> e" with "case L: return e" - replace case L -> block with "case L: block"** - m$switch() is declared to throw any checked exceptions thrown by switch arms. ** This is exploiting the otherwise-unfortunate pun between local return and nonlocal return. Where does this leave us? - Can't do nonlocal return out of an expression switch. That's good -- that's what we wanted anyway. - Can't reference mutable locals. That seems OK, but see below. - Fallthrough in expression switches (other than OR patterns) is cleverly prohibited by the syntax of switch expressions. This mental model leaves us pretty close to where we thought we wanted to land anyway (whether or not we actually translate like this.) Note that the ?effectively final? constraint is stronger than it needs to be to support this model. The reason that captured locals of even stack-confined lambdas need to be effectively final is that the capture point and the invocation point of the lambda might be different, and so there would be two credible answers for what the following program could print: int x = 3; Runnable r = () -> println(x); x = 4; r.run(); You could argue for either 3 or 4 here, so to eliminate the potential for confusion (among other reasons) we say ?no non-effectively-final captures?. But, for an expression switch desugared as above, the capture point and invocation point are guaranteed to be the same. So we could easily say that its OK to reference (but not mutate) mutable locals in a switch expression, and it would be consistent with the model given. Note that ?consistency? offers us no guidance here; in one approach we?re consistent with lambdas but inconsistent with, say, conditional expressions (which can reference and even mutate mutable locals.) So, candidates for what happens here: - like conditional expression ? anything goes, locals can be mutated - can read locals, but can?t write them ? a reasonable position, but unlike anything else so far - like lambdas - effectively final locals only. > On Dec 7, 2017, at 5:33 PM, Brian Goetz wrote: > > We've separated out a package of standalone improvements to `switch` (switch expressions, case null, and case alternation) into their own JEP: > > https://bugs.openjdk.java.net/browse/JDK-8192963 > > -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Sun Dec 10 18:59:36 2017 From: forax at univ-mlv.fr (Remi Forax) Date: Sun, 10 Dec 2017 19:59:36 +0100 (CET) Subject: New JEP: Switch Expressions for the Java Language In-Reply-To: <6F1D8934-488E-4643-A006-4D1CC57C70E7@oracle.com> References: <1932fb57-fda7-ac1d-57ec-2d062b56766e@oracle.com> <6F1D8934-488E-4643-A006-4D1CC57C70E7@oracle.com> Message-ID: <746568769.2856118.1512932376514.JavaMail.zimbra@u-pem.fr> > De: "Brian Goetz" > ?: "amber-spec-experts" > Envoy?: Dimanche 10 D?cembre 2017 18:56:51 > Objet: Re: New JEP: Switch Expressions for the Java Language > Here?s another way of looking at expression switch. This is a > translation-centric view, but may be useful both as a mental model and as a > specification approach as well. > If I have an expression switch: > T y = switch (x) { > case A -> e; > case B -> f; > } > We could implement by desugaring the switch into the invocation of a synthetic > method: > T y = m$switch(x, a, b, ...) > where a, b, ... are locals mentioned in e and f. And m$switch() is: > - replace "case L -> e" with "case L: return e" > - replace case L -> block with "case L: block"** > - m$switch() is declared to throw any checked exceptions thrown by switch arms. > ** This is exploiting the otherwise-unfortunate pun between local return and > nonlocal return. I prefer to think that instead of a call to a synthetic method, an invokedynamic that also takes a and b as argument is used. With an invokedynamic, you can reorganize the different cases (if the semantics of the expression switch allows that, and i believe it should) dynamically based on the dynamic values of x, something very similar to an inlining cache, which means in term of performance model that a virtual call and a call through an expression switch can be equivalent. Anyway, it changes nothing to the current discussion. > Where does this leave us? > - Can't do nonlocal return out of an expression switch. That's good -- that's > what we wanted anyway. > - Can't reference mutable locals. That seems OK, but see below. > - Fallthrough in expression switches (other than OR patterns) is cleverly > prohibited by the syntax of switch expressions. > This mental model leaves us pretty close to where we thought we wanted to land > anyway (whether or not we actually translate like this.) > Note that the ?effectively final? constraint is stronger than it needs to be to > support this model. The reason that captured locals of even stack-confined > lambdas need to be effectively final is that the capture point and the > invocation point of the lambda might be different, and so there would be two > credible answers for what the following program could print: > int x = 3; > Runnable r = () -> println(x); > x = 4; > r.run(); > You could argue for either 3 or 4 here, so to eliminate the potential for > confusion (among other reasons) we say ?no non-effectively-final captures?. > But, for an expression switch desugared as above, the capture point and > invocation point are guaranteed to be the same. So we could easily say that its > OK to reference (but not mutate) mutable locals in a switch expression, and it > would be consistent with the model given. I agree. The capture point and invocation point are the same if you do that in the correct order, you have to evaluate the expression of the switch before capturing the variables. Otherwise doing a side effect inside of the switch, int a = 3; switch(f(a = 4)) { case L -> a; } and you're in trouble. > Note that ?consistency? offers us no guidance here; in one approach we?re > consistent with lambdas but inconsistent with, say, conditional expressions > (which can reference and even mutate mutable locals.) Your talking about semantics consistency in general, it's not what a regular user see, the "syntax to semantics" consistency is more important, if the syntax use an arrow, then the semantics should be the same with the other(s) construct(s) that are using an arrow. > So, candidates for what happens here: > - like conditional expression ? anything goes, locals can be mutated > - can read locals, but can?t write them ? a reasonable position, but unlike > anything else so far > - like lambdas - effectively final locals only. yes, and there are several arguments against 2, as you wrote, "it's unlike anything else", it forces users to think where the capture occurs exactly (my example above) and it's not "syntax to semantics" consistent but this can be fixed by not using the arrow symbol. R?mi -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Sun Dec 10 19:40:30 2017 From: brian.goetz at oracle.com (Brian Goetz) Date: Sun, 10 Dec 2017 14:40:30 -0500 Subject: New JEP: Switch Expressions for the Java Language In-Reply-To: <746568769.2856118.1512932376514.JavaMail.zimbra@u-pem.fr> References: <1932fb57-fda7-ac1d-57ec-2d062b56766e@oracle.com> <6F1D8934-488E-4643-A006-4D1CC57C70E7@oracle.com> <746568769.2856118.1512932376514.JavaMail.zimbra@u-pem.fr> Message-ID: <27A87FD0-E010-449A-B488-BE120BDCEC3D@oracle.com> > > Otherwise doing a side effect inside of the switch, > int a = 3; > switch(f(a = 4)) { > case L -> a; > } > and you're in trouble. That?s implicitly choice (c) ? ?anything goes.? I think it is reasonable to restrict side-effects on locals ? were we designing conditional expressions today, we?d probably choose that too. If we outlaw such mutations, then such trouble can?t happen. > > Your talking about semantics consistency in general, it's not what a regular user see, the "syntax to semantics" consistency is more important, > if the syntax use an arrow, then the semantics should be the same with the other(s) construct(s) that are using an arrow. This is a good general principle, and among other things it means we should choose a syntax that is evocative of what we want to mean (which we?re not fully decided on yet). If what we mean is ?anything goes?, then maybe what we should conclude is that -> is a bad choice. (We certainly have ?justified? the uncomfortable ambiguity between local and nonlocal return by comparison with lambdas). I think all such choices are provisional until we?ve worked out both what we want to have happen, and a sensible way to express it, and then ask ourselves if we?re asking too much (both semantically and syntactically) of users. But lets not let the tail wag the dog. So, speaking semantically only: - We should not allow fall through in expression switch; I don?t think it makes any sense. - I?m pretty convinced that nonlocal returns out of expression switch (other than throwing) is similarly a wrong fit. (We allow this in neither conditional expressions nor lambdas.) - Switch expressions should definitely be able to reference locals, but we?re open minded to some restrictions (such as no mutation). - We could justify allowing switch expressions to mutate locals, since other expressions can too, but we could similarly justify restricting mutation - More strongly, we could justify restricting even referencing non-eff-final locals, though this is starting to get onto thin ice, because the only argument we have for this is ?for (superficial) consistency with lambdas?, which is pretty weak. > and there are several arguments against 2, as you wrote, "it's unlike anything else", it forces users to think where the capture occurs exactly (my example above) and it's not "syntax to semantics" consistent but this can be fixed by not using the arrow symbol. If mutation is restricted (which I think is really the first question), then there?s no confusion about "where the capture happens?. So perhaps we should examine the basis on which we are willing to justify restricting mutation. -------------- next part -------------- An HTML attachment was scrubbed... URL: From kevinb at google.com Mon Dec 11 18:10:50 2017 From: kevinb at google.com (Kevin Bourrillion) Date: Mon, 11 Dec 2017 13:10:50 -0500 Subject: New JEP: Switch Expressions for the Java Language In-Reply-To: <27A87FD0-E010-449A-B488-BE120BDCEC3D@oracle.com> References: <1932fb57-fda7-ac1d-57ec-2d062b56766e@oracle.com> <6F1D8934-488E-4643-A006-4D1CC57C70E7@oracle.com> <746568769.2856118.1512932376514.JavaMail.zimbra@u-pem.fr> <27A87FD0-E010-449A-B488-BE120BDCEC3D@oracle.com> Message-ID: On Sun, Dec 10, 2017 at 2:40 PM, Brian Goetz wrote: So, speaking semantically only: > - We should not allow fall through in expression switch; I don?t think it > makes any sense. > - I?m pretty convinced that nonlocal returns out of expression switch > (other than throwing) is similarly a wrong fit. (We allow this in neither > conditional expressions nor lambdas.) > - Switch expressions should definitely be able to reference locals, but > we?re open minded to some restrictions (such as no mutation). > - We could justify allowing switch expressions to mutate locals, since > other expressions can too, but we could similarly justify restricting > mutation > - More strongly, we could justify restricting even referencing > non-eff-final locals, though this is starting to get onto thin ice, because > the only argument we have for this is ?for (superficial) consistency with > lambdas?, which is pretty weak. > I believe I agree on all points. I think replacing : with -> is a very nice fit even if it's slightly more permissive than a lambda. Being just as restrictive does not seem justified. -- Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb at google.com -------------- next part -------------- An HTML attachment was scrubbed... URL: From kevinb at google.com Mon Dec 11 18:16:11 2017 From: kevinb at google.com (Kevin Bourrillion) Date: Mon, 11 Dec 2017 13:16:11 -0500 Subject: default branch placement in switch In-Reply-To: <4740da81-8d99-645f-fa22-f587b6433278@oracle.com> References: <4740da81-8d99-645f-fa22-f587b6433278@oracle.com> Message-ID: On Fri, Nov 3, 2017 at 5:25 PM, Brian Goetz wrote: > > or plan to eventually get to a place where default always comes last, even > for "int" switches. If we want to get to the latter, we should start > warning on this construct now. I favor starting to warn and eventually forbidding default in any position but last for all constructs that have it. A switch with the default in the middle is extremely weird and confusing. If I'm reading code to understand what happens when i == 3, and I read as far as switch (i) { case 1: justOneStuff(); break(); case 2: justTwoStuff(); break(); default: ... then I immediately assume that this must be where execution is continuing. Worse, even if I do notice that there are more case labels to follow, and I resume searching for a `case 3:`, then when I don't find one I now risk making *another* error and forgetting to jump *back* to the default. This is kind of insane. At first I was less worried because I thought "surely no one is actually doing this"... then I browsed our codebase.... yikes. We should at least strongly consider this. On 11/3/2017 5:10 PM, Tagir Valeev wrote: > >> Hello! >> >> Currently the default branch can be placed in any place inside the >> switch operator, e.g. like this: >> >> switch(i) { >> case 1: System.out.println("one");break; >> default: System.out.println("other");break; >> case 2: System.out.println("two");break; >> } >> >> In this case behavior does not change on the order of case blocks. >> However in pattern matching the order of cases usually matters: if >> some pattern matches, this means that the subsequent patterns will not >> be checked. Does this mean that with pattern matching the default >> branch makes all the subsequent case blocks unreachable? Or default >> can still be located anywhere and is checked only after any other >> pattern? >> >> With best regards, >> Tagir Valeev >> > > -- Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb at google.com -------------- next part -------------- An HTML attachment was scrubbed... URL: From kevinb at google.com Mon Dec 11 18:53:37 2017 From: kevinb at google.com (Kevin Bourrillion) Date: Mon, 11 Dec 2017 13:53:37 -0500 Subject: New JEP: Switch Expressions for the Java Language In-Reply-To: References: <1932fb57-fda7-ac1d-57ec-2d062b56766e@oracle.com> <6F1D8934-488E-4643-A006-4D1CC57C70E7@oracle.com> <746568769.2856118.1512932376514.JavaMail.zimbra@u-pem.fr> <27A87FD0-E010-449A-B488-BE120BDCEC3D@oracle.com> Message-ID: Reading the original proposal. First, I think it's great. I think a lot of existing switches would look nicer as expression switches. I think most of my questions surround how these changes will (or *should*) affect non-expression switches. - Should we retcon `case A, B:` onto regular switches, with the same semantics? - Should we retcon `case null:` onto regular switches, with the same semantics? - Should we make it possible to use the new expression switch construct in non-expression contexts? Any values returned by a case would be ignored. Optionally we could skip requiring default to be present (meh). Users may want this because it's really nice to not have to write "break" anymore. They may also want it if we answer "no" to either or both of the previous two questions. It's also kind of nice that each case gets its own separate scope, although they can get that by inserting braces anyway. - Allowing `throw` as the RHS of a case makes sense, but let's carefully think through the ramifications of making it an expression in general. Do we really want to see code like this? void foo(Bar bar) { bar.isValid() || throw new IllegalArgumentException(); } On Mon, Dec 11, 2017 at 1:10 PM, Kevin Bourrillion wrote: > > > On Sun, Dec 10, 2017 at 2:40 PM, Brian Goetz > wrote: > > So, speaking semantically only: >> - We should not allow fall through in expression switch; I don?t think >> it makes any sense. >> - I?m pretty convinced that nonlocal returns out of expression switch >> (other than throwing) is similarly a wrong fit. (We allow this in neither >> conditional expressions nor lambdas.) >> - Switch expressions should definitely be able to reference locals, but >> we?re open minded to some restrictions (such as no mutation). >> - We could justify allowing switch expressions to mutate locals, since >> other expressions can too, but we could similarly justify restricting >> mutation >> - More strongly, we could justify restricting even referencing >> non-eff-final locals, though this is starting to get onto thin ice, because >> the only argument we have for this is ?for (superficial) consistency with >> lambdas?, which is pretty weak. >> > > I believe I agree on all points. I think replacing : with -> is a very > nice fit even if it's slightly more permissive than a lambda. Being just as > restrictive does not seem justified. > > > -- > Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb at google.com > -- Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb at google.com -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Mon Dec 11 19:15:42 2017 From: brian.goetz at oracle.com (Brian Goetz) Date: Mon, 11 Dec 2017 14:15:42 -0500 Subject: default branch placement in switch In-Reply-To: References: <4740da81-8d99-645f-fa22-f587b6433278@oracle.com> Message-ID: The middle is surely awful.? Though in the JDK, we have a fair number of uses where default is the _first_ case, which isn't unreasonable, and some might argue is even clearer in some cases. The reason to tread lightly on forcing reorganization of existing switches is that it is allowable to fall into *and out of* the default case.? So if someone has: ??? switch (x) { ??????? default:? S; ? // fall through ??????? case COMMON: T; break; ??????? case UNCOMMON: U; break; ??? } then eventually getting to an error when default is not last for "legacy" switches (those where all labels are type-restating constants) means some uncomfortable refactoring just to "make the compiler happy."? So while I agree on warnings, I'm not sure if we can ever get to error in all cases without picking some fights with users. On 12/11/2017 1:16 PM, Kevin Bourrillion wrote: > On Fri, Nov 3, 2017 at 5:25 PM, Brian Goetz > wrote: > > > or plan to eventually get to a place where default always comes > last, even for "int" switches. If we want to get to the latter, we > should start warning on this construct now. > > > I favor starting to warn and eventually forbidding default in any > position but last for all constructs that have it. > > A switch with the default in the middle is extremely weird and > confusing. If I'm reading code to understand what happens when i == 3, > and I read as far as > > switch (i) { > ? case 1: > ? ? justOneStuff(); break(); > ? case 2: > ? ? justTwoStuff(); break(); > ? default: > > ... then I immediately assume that this must be where execution is > continuing. Worse, even if I do notice that there are more case labels > to follow, and I resume searching for a `case 3:`, then when I don't > find one I now risk making /another/?error and forgetting to jump > /back/ to the default. > > This is kind of insane. At first I was less worried because I thought > "surely no one is actually doing this"... then I browsed our > codebase.... yikes. > > We should at least strongly consider this. > > > > On 11/3/2017 5:10 PM, Tagir Valeev wrote: > > Hello! > > Currently the default branch can be placed in any place inside the > switch operator, e.g. like this: > > switch(i) { > case 1: System.out.println("one");break; > default: System.out.println("other");break; > case 2: System.out.println("two");break; > } > > In this case behavior does not change on the order of case blocks. > However in pattern matching the order of cases usually matters: if > some pattern matches, this means that the subsequent patterns > will not > be checked. Does this mean that with pattern matching the default > branch makes all the subsequent case blocks unreachable? Or > default > can still be located anywhere and is checked only after any other > pattern? > > With best regards, > Tagir Valeev > > > > > > -- > Kevin Bourrillion?|?Java Librarian |?Google, Inc.?|kevinb at google.com > -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Mon Dec 11 19:28:17 2017 From: brian.goetz at oracle.com (Brian Goetz) Date: Mon, 11 Dec 2017 14:28:17 -0500 Subject: New JEP: Switch Expressions for the Java Language In-Reply-To: References: <1932fb57-fda7-ac1d-57ec-2d062b56766e@oracle.com> <6F1D8934-488E-4643-A006-4D1CC57C70E7@oracle.com> <746568769.2856118.1512932376514.JavaMail.zimbra@u-pem.fr> <27A87FD0-E010-449A-B488-BE120BDCEC3D@oracle.com> Message-ID: > Reading the original proposal. > > First, I think it's great. I think a lot of existing switches would > look nicer as expression switches. ... and be less error-prone. > > I think most of my questions surround how these changes will (or > /should/) affect non-expression switches. > > * Should we retcon `case A, B:` onto regular switches, with the same > semantics? > Absolutely; I thought this was already present in the JEP, but if not, I'll clarify. > * Should we retcon `case null:` onto regular switches, with the same > semantics? > Absolutely; same comment. > * Should we make it possible to use the new expression switch > construct in non-expression contexts? Any values returned by a > case would be ignored. Optionally we could skip requiring default > to be present (meh). Users may want this because it's really nice > to not have to write "break" anymore. They may also want it if we > answer "no" to either or both of the previous two questions. It's > also kind of nice that each case gets its own separate scope, > although they can get that by inserting braces anyway. > I think this sort of falls out for free; expressions are generally usable as statements, unless we can't determine their standalone type (like lambdas).? However, I'm not sure this would be good practice. > * Allowing `throw` as the RHS of a case makes sense, but let's > carefully think through the ramifications of making it an > expression in general. Do we really want to see code like this? > > void foo(Bar bar) { > ? bar.isValid() || throw new IllegalArgumentException(); > } > OK, let's pull on that string.? The next logical place for it is conditional expressions: ??? String s = (x >= 0) ? Integer.toString(x) : throw new IAE(); This seems pretty reasonable too, though the if-else version is not horribly broken either.? What about conditionals in method invocation context: ??? m(x > 0 ? Integer.toString(x) : throw new IAE()) I know I've wanted to be able to do this at times, though again, I've worked around it and lived, and surely it could be abused. I'm OK cutting this one back. -------------- next part -------------- An HTML attachment was scrubbed... URL: From kevinb at google.com Mon Dec 11 19:38:11 2017 From: kevinb at google.com (Kevin Bourrillion) Date: Mon, 11 Dec 2017 11:38:11 -0800 Subject: New JEP: Switch Expressions for the Java Language In-Reply-To: References: <1932fb57-fda7-ac1d-57ec-2d062b56766e@oracle.com> <6F1D8934-488E-4643-A006-4D1CC57C70E7@oracle.com> <746568769.2856118.1512932376514.JavaMail.zimbra@u-pem.fr> <27A87FD0-E010-449A-B488-BE120BDCEC3D@oracle.com> Message-ID: On Mon, Dec 11, 2017 at 11:28 AM, Brian Goetz wrote: > > Reading the original proposal. > > First, I think it's great. I think a lot of existing switches would look > nicer as expression switches. > > > ... and be less error-prone. > > > I think most of my questions surround how these changes will (or *should*) > affect non-expression switches. > > - Should we retcon `case A, B:` onto regular switches, with the same > semantics? > > Absolutely; I thought this was already present in the JEP, but if not, > I'll clarify. > > > - Should we retcon `case null:` onto regular switches, with the same > semantics? > > > Absolutely; same comment. > > > - Should we make it possible to use the new expression switch > construct in non-expression contexts? Any values returned by a case would > be ignored. Optionally we could skip requiring default to be present (meh). > Users may want this because it's really nice to not have to write "break" > anymore. They may also want it if we answer "no" to either or both of the > previous two questions. It's also kind of nice that each case gets its own > separate scope, although they can get that by inserting braces anyway. > > > I think this sort of falls out for free; expressions are generally usable > as statements, unless we can't determine their standalone type (like > lambdas). However, I'm not sure this would be good practice. > To be more clear, I think what I'm asking is whether we would permit cases to not produce a value, if the entire switch were not being used as an expression. Doing this would kind of sort of make the original switch statement relatively obsolete. That feels weird, but on the other hand, maybe being able to consistently use new-style switches more and more of the time would be a good thing? Just thinking out loud here. > - Allowing `throw` as the RHS of a case makes sense, but let's > carefully think through the ramifications of making it an expression in > general. Do we really want to see code like this? > > void foo(Bar bar) { > bar.isValid() || throw new IllegalArgumentException(); > } > > > OK, let's pull on that string. The next logical place for it is > conditional expressions: > > String s = (x >= 0) ? Integer.toString(x) : throw new IAE(); > > This seems pretty reasonable too, though the if-else version is not > horribly broken either. What about conditionals in method invocation > context: > > m(x > 0 ? Integer.toString(x) : throw new IAE()) > > I know I've wanted to be able to do this at times, though again, I've > worked around it and lived, and surely it could be abused. > This is like a buried lede. A major function of this statement is to make your entire method bomb out if x is negative - but that's randomly embedded inside some method call site. I don't think I like this. > I'm OK cutting this one back. > -- Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb at google.com -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Mon Dec 11 20:03:54 2017 From: brian.goetz at oracle.com (Brian Goetz) Date: Mon, 11 Dec 2017 15:03:54 -0500 Subject: New JEP: Switch Expressions for the Java Language In-Reply-To: References: <1932fb57-fda7-ac1d-57ec-2d062b56766e@oracle.com> <6F1D8934-488E-4643-A006-4D1CC57C70E7@oracle.com> <746568769.2856118.1512932376514.JavaMail.zimbra@u-pem.fr> <27A87FD0-E010-449A-B488-BE120BDCEC3D@oracle.com> Message-ID: <939d7029-35df-ca57-ce31-f2d6a95f9e7e@oracle.com> > I think this sort of falls out for free; expressions are generally > usable as statements, unless we can't determine their standalone type > (like lambdas).? However, I'm not sure this would be good practice. I'm not sure either.? Ignoring the value of an expression is often a smell. BTW I don't think people will do this just because they hate break; the alternative is worse (see message on similar topic on amber-dev.) > To be more clear, I think what I'm asking is whether we would permit > cases to not produce a value, if the entire switch were not being used > as an expression. Doing this would kind of sort of make the original > switch statement relatively obsolete. That feels weird, but on the > other hand, maybe being able to consistently use new-style switches > more and more of the time would be a good thing? I think we need more examples, either in favor or against.... -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Mon Dec 11 20:15:43 2017 From: brian.goetz at oracle.com (Brian Goetz) Date: Mon, 11 Dec 2017 15:15:43 -0500 Subject: New JEP: Switch Expressions for the Java Language In-Reply-To: References: <1932fb57-fda7-ac1d-57ec-2d062b56766e@oracle.com> <6F1D8934-488E-4643-A006-4D1CC57C70E7@oracle.com> <746568769.2856118.1512932376514.JavaMail.zimbra@u-pem.fr> <27A87FD0-E010-449A-B488-BE120BDCEC3D@oracle.com> Message-ID: > >> * Should we make it possible to use the new expression switch >> construct in non-expression contexts? Any values returned by a >> case would be ignored. Optionally we could skip requiring default >> to be present (meh). Users may want this because it's really nice >> to not have to write "break" anymore. They may also want it if we >> answer "no" to either or both of the previous two questions. It's >> also kind of nice that each case gets its own separate scope, >> although they can get that by inserting braces anyway. >> In order for it not to be painful to turn a statement switch into an expression switch, we'd have to support void-typed expression switches.? Then you could write something like this: ??? switch (target) { ? ?? ?? case X -> println("x"); ? ?? ?? case Y -> println("y"); ??? } But expressions can't have void type (among other reasons, it's not a type, at least not yet), so we'd have to carve out a special case for this if we wanted to support it.? And this would further muddy the differences, and call for a number of other special cases (e.g., how would I write the default clause?) So as long as we don't bend over backwards to try and special-case void expression switches (we don't for conditional expressions), I think "there's expression switch and statement switch; using either to simulate the other is painful, so don't" leads to a good place. (Note to observers: the goal of this exercise here is not to fix what people hate about statement switch; it's about (a) laying the groundwork for pattern matching while (b) factoring out some generally-useful enhancements that can be exposed earlier.) -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Mon Dec 11 21:37:57 2017 From: brian.goetz at oracle.com (Brian Goetz) Date: Mon, 11 Dec 2017 16:37:57 -0500 Subject: Switch translation, part 2 Message-ID: <73805383-7ae7-fc47-1a21-f7116b78248a@oracle.com> # Switch Translation, Part 2 -- type test patterns and guards #### Maurizio Cimadamore and Brian Goetz #### December 2017 This document examines possible translation of `switch` constructs involving `case` labels that include type-test patterns, potentially with guards.? Part 3 will address translation of destructuring patterns, nested patterns, and OR patterns. ## Type-test patterns Type-test patterns are notable because their applicability predicate is purely based on the type system, meaning that the compiler can directly reason about it both statically (using flow analysis, optimizing away dynamic type tests) and dynamically (with `instanceof`.)? A switch involving type-tests: ??? switch (x) { ??????? case String s: ... ??????? case Integer i: ... ??????? case Long l: ... ??? } can (among other strategies) be translated into a chain of `if-else` using `instanceof` and casts: ??? if (x instanceof String) { String s = (String) x; ... } ??? else if (x instanceof Integer) { Integer i = (Integer) x; ... } ??? else if (x instanceof Long) { Long l = (Long) x; ... } #### Guards The `if-else` desugaring can also naturally handle guards: ??? switch (x) { ??????? case String s ??????????? where (s.length() > 0): ... ??????? case Integer i ??????????? where (i > 0): ... ??????? case Long l ??????????? where (l > 0L): ... ??? } can be translated to: ??? if (x instanceof String ??????? && ((String) x).length() > 0) { String s = (String) x; ... } ??? else if (x instanceof Integer ???????????? && ((Integer) x) > 0) { Integer i = (Integer) x; ... } ??? else if (x instanceof Long ???????????? && ((Long) x) > 0L) { Long l = (Long) x; ... } #### Performance concerns The translation to `if-else` chains is simple (for switches without fallthrough), but is harder for the VM to optimize, because we've used a more general control flow mechanism.? If the target is an empty `String`, which means we'd pass the first `instanceof` but fail the guard, class-hierarchy analysis could tell us that it can't possibly be an `Integer` or a `Long`, and so there's no need to perform those tests. But generating code that takes advantage of this information is more complex. In the extreme case, where a switch consists entirely of type test patterns for final classes, this could be performed as an O(1) operation by hashing.? And this is a common case involving switches over alternatives in a sum (sealed) type. (We probably shouldn't rely on finality at compile time, as this can change between compile and run time, but we would like to take advantage of this at run time if we can.) Finally, the straightforward static translation may miss opportunities for optimization.? For example: ??? switch (x) { ??????? case Point p ??????????? where p.x > 0 && p.y > 0: A ??????? case Point p ??????????? where p.x > 0 && p.y == 0: B ??? } Here, not only would we potentially test the target twice to see if it is a `Point`, but we then further extract the `x` component twice and perform the `p.x > 0` test twice. #### Optimization opportunities The compiler can eliminate some redundant calculations through straightforward techniques.? The previous switch can be transformed to: ??? switch (x) { ??????? case Point p: ??????????? if (((Point) p).x > 0 && ((Point) p).y > 0) { A } ??????????? else if (((Point) p).x > 0 && ((Point) p).y > 0) { B } to eliminate the redundant `instanceof` (and could be further transformed to eliminate the downstream redundant computations.) #### Clause reordering The above example was easy to transform because the two `case Point` clauses were adjacent.? But what if they are not?? In some cases, it is safe to reorder them.? For types `T` and `U`, it is safe to reorder `case T` and `case U` if the two types have no intersection; that there can be no types that are subtypes of them both.? This is true when `T` and `U` are classes and neither extends the other, or when one is a final class and the other is an interface that the class does not implement. The compiler could then reorder case clauses so that all the ones whose first test is `case Point` are adjacent, and then coalesce them all into a single arm of the `if-else` chain. A possible spoiler here is fallthrough; if case A falls into case B, then cases A and B have to be moved as a group.? (This is another reason to consider limiting fallthrough.) #### Summary of if-else translation While the if-else translation at first looks pretty bad, we are able to extract a fair amount of redundancy through well-understood compiler transformations.? If an N-way switch has only M distinct types in it, in most cases we can reduce the cost from _O(N)_ to _O(M)_.? Sometimes _M == N_, so this doesn't help, but sometimes _M << N_ (and sometimes `N` is small, in which case _O(N)_ is fine.) Reordering clauses involves some risk; specifically, that the class hierarchy will change between compile and run time.? It seems eminently safe to reorder `String` and `Integer`, but more questionable to reorder an arbitrary class `Foo` with `Runnable`, even if `Foo` doesn't implement `Runnable` now, because it might easily be changed to do so later.? Ideally we'd like to perform class-hierarchy optimizations using the runtime hierarchy, not the compile-time hierarchy. ## Type classifiers The technique outlined in _Part 1_, where we lower the complex switch to a dense `int` switch, and use an indy-based classifier to select an index, is applicable here as well.? First let's consider a switch consisting only of unguarded type-test patterns (and optionally a default clause.) We'll start with an `indy` bootstrap whose static argument are `Class` constants corresponding to each arm of the switch, whose dynamic argument is the switch target, and whose return value is a case number (or distinguished sentinels for "no match" and `null`.) We can easily implement such a bootstrap with a linear search, but can also do better; if some subset of the classes are `final`, we can choose between these more quickly (such as via binary search on `hashCode()`, hash function, or hash table), and we need perform only a single operation to test all of those at once. Dynamic techniques (such as a building a hash map of previously seen target types), which `indy` is well-suited to, can asymptotically approach _O(1)_ even when the classes involved are not final. So we can lower: ??? switch (x) { ??????? case T t: A ??????? case U u: B ??????? case V v: C ??? } to ??? int y = indy[bootstrap=typeSwitch(T.class, U.class, V.class)](x) ??? switch (y) { ??????? case 0: A ??????? case 1: B ??????? case 2: C ??? } This has the advantages that the generated code is very similar to the source code, we can (in some cases) get _O(1)_ dispatch performance, and we can handle fallthrough with no additional complexity. #### Guards There are two approaches we could take to add support for guards into the process; we could try to teach the bootstrap about guards (and would have to pass locals that appear in guard expressions as additional arguments to the classifier), or we could leave guards to the generated bytecode.? The latter seems far more attractive, but requires some tweaks to the bootstrap arguments and to the shape of the generated code. If the classifier says "you have matched case #3", but then we fail the guard for #3, we want to go back into the classifier and start again at #4.? Additionally, we'd like for the classifier to use this information ("start over at #4") to optimize away unnecessary tests. We add a second argument (where to start) to the classifier invocation signature, and wrap the switch in a loop, lowering: ??? switch (x) { ??????? case T t where (e1): A ??????? case T t where (e2): B ??????? case U u where (e3): C ??? } into ??? int y = -1; // start at the top ??? while (true) { ??????? y = indy[...](x, y) ? ? ??? switch (y) { ?? ? ?????? case 0: if (!e1) continue; A ? ?? ?????? case 1: if (!e2) continue; B ? ?? ?????? case 2: if (!e3) continue; C ??????? } ??????? break; ??? } For cases where the same type test is repeated in consecutive positions (at N and N+1), we can have the static compiler coalesce them as above, or we could have the bootstrap maintain a table so that if you re-enter the bootstrap where the previous answer was N, then it can immediately return N+1.? Similarly, if N and N+1 are known to be mutually exclusive types (like `String` and `Integer`), on reentering the classifier with N, we can skip right to N+2 since if we matched `String`, we cannot match `Integer`. Lookup tables for such optimizations can be built at link time. #### Mixing constants and type tests This approach also extends to tests that are a mix of constant patterns and type-test patterns, such as: ??? switch (x) { ??????? case "Foo": ... ??????? case 0L: ... ??????? case Integer i: ??? } We can extend the bootstrap protocol to accept constants as well as types, and it is a straightforward optimization to combine both type matching and constant matching in a single pass. -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Mon Dec 11 21:25:34 2017 From: brian.goetz at oracle.com (Brian Goetz) Date: Mon, 11 Dec 2017 16:25:34 -0500 Subject: Switching on float/double/long Message-ID: A target of opportunity for the new switch JEP is to fill out the set of types that traditional switches can operate on -- specifically float, double, and long.? The reason that we don't support these now is mostly an accident of history; the `tableswitch` and `lookupswitch` opcodes are int-based, so the compiler doesn't have a convenient target for translating these. As you've seen from the recent notes on switch translation, we're working towards using indy more broadly as a translation target for most switch constructs.? This makes it far easier to bust the limitations on switch argument types, and so this has been listed as a target of opportunity in the JEP (for both statement and expression switches.) Our resident floating-point expert, Joe Darcy, offers the following additional thoughts on the subject: -- Begin forwarded message Per a recent request from Brian, I've written a few thoughts about switching on floating-point values. To address some common misunderstandings of floating-point, while it is often recommended to *not* compare floating-point values for equality, it is perfectly well-defined to do such comparisons, it just might not do what you want For example, instead of ??? // Infinite loop since sum stored in d never exactly equals 1.0, doh! ??? while(d != 1.0) ??????? d += 0.1; use either ??? // Counted loop ??? for(int i = 0; i < 10; i++) ??????? d += 0.1; or ??? // Stop when numerical threshold is met ??? while(d <= 1.0) ??????? d += 0.1; depending on the semantics the loop is trying to capture. I've attached a slide from my JVMLS talk this year to help illustrate the semantic modeling going in in IEEE 754 floating-point. Each of the 2^32 possible bit patterns of a float is some floating-point value, likewise for the 2^64 possible bit patterns of a double. However, from a Java language or JVM perspective, there are not 2^32 or 2^64 distinct values we need or want to distinguish in most cases. In particular, we almost always want to treat all bit patterns which encode a NaN as a single conceptual NaN. Another wrinkle concerns zero: IEEE 754 has both a positive zero and a negative zero. Why are there *two* zeros? Because there are two infinities.? The signed infinities and distinguished by divide (1.0/0.0 => +infinity, 1.0/-0.0 => -infinity) and by various library functions. So we want to: ??? * Allow every distinct finite nonzero floating-point value to be? the case of a switch. ??? * Allow -0.0 and +0.0 to be treated separately. ??? * Allow -infinity and +infinity to be treated separately. ??? * Collapse all NaN representation as a single value. For the "Rounding" mapping in the diagram which goes from the extended real numbers to floating-point data, there is a nonempty segment of the real number line which maps to a given representable floating-point number. For example, besides the string "1.0" mapping exactly to the reprentable floating-point value 1.0, there is a region slightly small than 1 (0.99999999999999999999...) which will round up to 1.0 and a region slightly larger than 1 (1.000000000000000001...) which will round down to 1 from decimal -> binary conversion. This would need to be factored into any distinctiveness requirements for the different arms of the switch. In other words ??? case 1.000000000000000001: ??? .... ??? case 0.99999999999999999999 ??? ... would need to be rejected just as ??? case 0: ??? .... ??? case 00: is rejected. In terms of JDK 9 structures and operations, the following transformation of a float switch has what I think are reasonable semantics: ??? Replace each float case label y in the source with an int label resulting from floatToIntBits(y). Note that floatToIntBits is used for the mapping rather than floatToRawIntBits since we want NaNs to be grouped together. ??? Instead of switching on float value x, switch on floatToIntBits(x). HTH, -Joe -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Tue Dec 12 13:43:22 2017 From: forax at univ-mlv.fr (Remi Forax) Date: Tue, 12 Dec 2017 14:43:22 +0100 (CET) Subject: New JEP: Switch Expressions for the Java Language In-Reply-To: <964faaf6-65ca-5a05-8f1f-77ac1fe841d9@oracle.com> References: <964faaf6-65ca-5a05-8f1f-77ac1fe841d9@oracle.com> Message-ID: <1633904609.769787.1513086202881.JavaMail.zimbra@u-pem.fr> ----- Mail original ----- > De: "Brian Goetz" > ?: "Stephen Colebourne" , "amber-dev" > Envoy?: Lundi 11 D?cembre 2017 20:59:27 > Objet: Re: New JEP: Switch Expressions for the Java Language > On 12/11/2017 7:04 AM, Stephen Colebourne wrote: >> At first, my reaction was "great!", but after doing some analysis I'm >> left feeling this isn't going to work well as proposed... >> >> To summarise the proposal, expression switches get: >> - comma separated OR values >> - case null >> - no fall through >> - enforced default >> - auto default clause for enums >> >> And statement switches get none of these (AFAICT). > > Incorrect on the first two items.? JEP says: > > repeated labels: "A case clause *in a switch statement or expression > *can provide a comma-separated list of alternatives, such as:" > > null: "In *a switch expression **or statement *whose whose argument is a > reference type (boxed primitives, strings, and enums), a case clause can > can specify null:" > > On the other three, these are driven by either intrinsic properties of > expression-ness, or compatibility: > ?- no fall through -- well, fall through doesn't make sense (in its > unvarnished form) in expression switch. > ?- enforced default -- expressions must be total; conditional > statements can be unbalanced (like if with no else) > ?- auto default clause for enums -- can't do that to statement switch, > it would change the semantics of existing code. [...] to summarize, we enhance le old switch by adding comma separated list of case values and by adding null as a possible pattern, and a warning if there is no default or the default is not at the ends, then we add an expression switch which extends the old switch as a poly expression with no fall through (make no sense), no weird scoping rules, the compiler emits an error if the switch is not exhaustive (need a default at the end apart for enums). what i'm currently doing with switch is using statement switch + return as a poor's man expression switch i.e instead of Color color; switch(name) { case "red": color = Color.RED; break; case "blue": color = Color.BLUE; break; default: throw new AssertionError("invalid " + name); } ... I tend to refactor by introducing a method Color color = getColorFromName(name); ... private static Color getColorFromName(String name) { switch(name) { case "red": return Color.RED; case "blue": return Color.BLUE; default: throw new AssertionError("invalid " + name); } } which provides most of the guarantee that provide the expression switch, you ends the case with a return, so no fallthrough, using return make it a poly-expression (BTW, why do we need to specify the name of the enum class in a poly-expression ?), no weird scoping rule, the compiles forces you to write a 'default', otherwise it doesn't compile. So when we have started this discussion, i've seen the expression switch as a way to simplify this pattern, by avoiding to write the switch in a private method Color color = switch(name) { case "red" -> Color.RED; case "blue" -> Color.BLUE; default: throw new AssertionError("invalid " + name); }; But if we support expression block in the expression switch, the two forms are very close syntactically, by example, can you find the difference between return switch(name) { case "red" -> { return Color.RED; }; case "blue" -> { return Color.BLUE; }; default -> { throw new AssertionError("invalid " + name); }; }; and switch(name) { case "red": { return Color.RED; }; case "blue": { return Color.BLUE; }; default: { throw new AssertionError("invalid " + name); }; }; this is far from obvious, and as a dev you have to because the semantics are differents. I wonder if we should either make the syntactic difference more clear, Stephen proposes switch ... when but what if there is no case at all, IMO the syntax should not even starts with switch, or we can ban expression block inside an expression switch. regards, R?mi From brian.goetz at oracle.com Tue Dec 12 14:25:07 2017 From: brian.goetz at oracle.com (Brian Goetz) Date: Tue, 12 Dec 2017 09:25:07 -0500 Subject: New JEP: Switch Expressions for the Java Language In-Reply-To: <1633904609.769787.1513086202881.JavaMail.zimbra@u-pem.fr> References: <964faaf6-65ca-5a05-8f1f-77ac1fe841d9@oracle.com> <1633904609.769787.1513086202881.JavaMail.zimbra@u-pem.fr> Message-ID: <61ee5451-6dd0-8917-6421-ee4ee3a637ca@oracle.com> > to summarize, > we enhance le old switch by adding comma separated list of case values and by adding null as a possible pattern, and a warning if there is no default or the default is not at the ends, > then we add an expression switch which extends the old switch as a poly expression with no fall through (make no sense), no weird scoping rules, the compiler emits an error if the switch is not exhaustive (need a default at the end apart for enums). Right. > what i'm currently doing with switch is using statement switch + return as a poor's man expression switch Very common! > I tend to refactor by introducing a method Essentially, trying to make DA work for you. > But if we support expression block in the expression switch, the two forms are very close syntactically, > by example, can you find the difference between > return switch(name) { > case "red" -> { > return Color.RED; > }; > case "blue" -> { > return Color.BLUE; > }; > default -> { > throw new AssertionError("invalid " + name); > }; > }; > and > switch(name) { > case "red": { > return Color.RED; > }; > case "blue": { > return Color.BLUE; > }; > default: { > throw new AssertionError("invalid " + name); > }; > }; > this is far from obvious, and as a dev you have to because the semantics are differents. The main difference is the unfortunate pun between local and nonlocal return. > I wonder if we should either make the syntactic difference more clear Open to suggestions. From forax at univ-mlv.fr Tue Dec 12 21:52:52 2017 From: forax at univ-mlv.fr (Remi Forax) Date: Tue, 12 Dec 2017 22:52:52 +0100 (CET) Subject: Switching on float/double/long In-Reply-To: References: Message-ID: <1801453083.1035590.1513115572265.JavaMail.zimbra@u-pem.fr> While we could do that, use bits representation for float and double, this is typically the kind of things that a user can also do with a record (a value type record ?) and a deconstructor, so in my opinion, we should not rush to implement this kind of switch given that we will soon provide a general mechanism to implement them outside of the JDK. R?mi > De: "Brian Goetz" > ?: "amber-spec-experts" > Envoy?: Lundi 11 D?cembre 2017 22:25:34 > Objet: Switching on float/double/long > A target of opportunity for the new switch JEP is to fill out the set of types > that traditional switches can operate on -- specifically float, double, and > long. The reason that we don't support these now is mostly an accident of > history; the `tableswitch` and `lookupswitch` opcodes are int-based, so the > compiler doesn't have a convenient target for translating these. As you've seen > from the recent notes on switch translation, we're working towards using indy > more broadly as a translation target for most switch constructs. This makes it > far easier to bust the limitations on switch argument types, and so this has > been listed as a target of opportunity in the JEP (for both statement and > expression switches.) > Our resident floating-point expert, Joe Darcy, offers the following additional > thoughts on the subject: > -- Begin forwarded message > Per a recent request from Brian, I've written a few thoughts about switching on > floating-point values. > To address some common misunderstandings of floating-point, while it is often > recommended to * not * compare floating-point values for equality, it is > perfectly well-defined to do such comparisons, it just might not do what you > want > For example, instead of > // Infinite loop since sum stored in d never exactly equals 1.0, doh! > while(d != 1.0)\u000B > d += 0.1; > use either > // Counted loop > for(int i = 0; i < 10; i++)\u000B > d += 0.1; > or > // Stop when numerical threshold is met > while(d <= 1.0)\u000B > d += 0.1; > depending on the semantics the loop is trying to capture. > I've attached a slide from my JVMLS talk this year to help illustrate the > semantic modeling going in in IEEE 754 floating-point. Each of the 2 ^ 32 > possible bit patterns of a float is some floating-point value, likewise for the > 2 ^ 64 possible bit patterns of a double. However, from a Java language or JVM > perspective, there are not 2 ^ 32 or 2 ^ 64 distinct values we need or want to > distinguish in most cases. In particular, we almost always want to treat all > bit patterns which encode a NaN as a single conceptual NaN. Another wrinkle > concerns zero: IEEE 754 has both a positive zero and a negative zero. Why are > there * two * zeros? Because there are two infinities. The signed infinities > and distinguished by divide (1.0/0.0 => +infinity, 1.0/-0.0 => -infinity) and > by various library functions. > So we want to: > * Allow every distinct finite nonzero floating-point value to be the case of a > switch. > * Allow -0.0 and +0.0 to be treated separately. > * Allow -infinity and +infinity to be treated separately. > * Collapse all NaN representation as a single value. > For the "Rounding" mapping in the diagram which goes from the extended real > numbers to floating-point data, there is a nonempty segment of the real number > line which maps to a given representable floating-point number. For example, > besides the string "1.0" mapping exactly to the reprentable floating-point > value 1.0, there is a region slightly small than 1 (0.99999999999999999999...) > which will round up to 1.0 and a region slightly larger than 1 > (1.000000000000000001...) which will round down to 1 from decimal -> binary > conversion. This would need to be factored into any distinctiveness > requirements for the different arms of the switch. In other words > case 1.000000000000000001: > .... > case 0.99999999999999999999 > ... > would need to be rejected just as > case 0: > .... > case 00: > is rejected. > In terms of JDK 9 structures and operations, the following transformation of a > float switch has what I think are reasonable semantics: > Replace each float case label y in the source with an int label resulting from > floatToIntBits(y). Note that floatToIntBits is used for the mapping rather than > floatToRawIntBits since we want NaNs to be grouped together. > Instead of switching on float value x, switch on floatToIntBits(x). > HTH, > -Joe -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Tue Dec 12 22:08:45 2017 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Tue, 12 Dec 2017 23:08:45 +0100 (CET) Subject: New JEP: Switch Expressions for the Java Language In-Reply-To: <61ee5451-6dd0-8917-6421-ee4ee3a637ca@oracle.com> References: <964faaf6-65ca-5a05-8f1f-77ac1fe841d9@oracle.com> <1633904609.769787.1513086202881.JavaMail.zimbra@u-pem.fr> <61ee5451-6dd0-8917-6421-ee4ee3a637ca@oracle.com> Message-ID: <1771986284.1057180.1513116525734.JavaMail.zimbra@u-pem.fr> ----- Mail original ----- > De: "Brian Goetz" > ?: "Remi Forax" , "amber-spec-experts" > Cc: "Stephen Colebourne" > Envoy?: Mardi 12 D?cembre 2017 15:25:07 > Objet: Re: New JEP: Switch Expressions for the Java Language >> to summarize, >> we enhance le old switch by adding comma separated list of case values and by >> adding null as a possible pattern, and a warning if there is no default or the >> default is not at the ends, >> then we add an expression switch which extends the old switch as a poly >> expression with no fall through (make no sense), no weird scoping rules, the >> compiler emits an error if the switch is not exhaustive (need a default at the >> end apart for enums). > > Right. > >> what i'm currently doing with switch is using statement switch + return as a >> poor's man expression switch > > Very common! >> I tend to refactor by introducing a method > Essentially, trying to make DA work for you. >> But if we support expression block in the expression switch, the two forms are >> very close syntactically, >> by example, can you find the difference between >> return switch(name) { >> case "red" -> { >> return Color.RED; >> }; >> case "blue" -> { >> return Color.BLUE; >> }; >> default -> { >> throw new AssertionError("invalid " + name); >> }; >> }; >> and >> switch(name) { >> case "red": { >> return Color.RED; >> }; >> case "blue": { >> return Color.BLUE; >> }; >> default: { >> throw new AssertionError("invalid " + name); >> }; >> }; >> this is far from obvious, and as a dev you have to because the semantics are >> differents. > > The main difference is the unfortunate pun between local and nonlocal > return. > >> I wonder if we should either make the syntactic difference more clear > > Open to suggestions. Use 'when' or 'match', with no 'case' and '_' instead of 'default' like in Kotlin/Scala (Koala) ? return match(name) { "red" -> Color.RED; "blue" -> { return Color.BLUE; } _ -> throw new AssertionError("invalid " + name); }; with the curly braces after match, it's not a valid method call so 'match' can be local keyword. cheers, R?mi From forax at univ-mlv.fr Wed Dec 13 10:08:04 2017 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Wed, 13 Dec 2017 11:08:04 +0100 (CET) Subject: New JEP: Switch Expressions for the Java Language In-Reply-To: References: <1932fb57-fda7-ac1d-57ec-2d062b56766e@oracle.com> <6F1D8934-488E-4643-A006-4D1CC57C70E7@oracle.com> <746568769.2856118.1512932376514.JavaMail.zimbra@u-pem.fr> <27A87FD0-E010-449A-B488-BE120BDCEC3D@oracle.com> Message-ID: <1340045638.1183461.1513159684203.JavaMail.zimbra@u-pem.fr> > De: "Brian Goetz" > ?: "Kevin Bourrillion" > Cc: "Remi Forax" , "amber-spec-experts" > > Envoy?: Lundi 11 D?cembre 2017 20:28:17 > Objet: Re: New JEP: Switch Expressions for the Java Language [...] >> * Allowing `throw` as the RHS of a case makes sense, but let's carefully think >> through the ramifications of making it an expression in general. Do we really >> want to see code like this? void foo(Bar bar) { bar.isValid() || throw new >> IllegalArgumentException(); } > OK, let's pull on that string. The next logical place for it is conditional > expressions: > String s = (x >= 0) ? Integer.toString(x) : throw new IAE(); > This seems pretty reasonable too, though the if-else version is not horribly > broken either. What about conditionals in method invocation context: > m(x > 0 ? Integer.toString(x) : throw new IAE()) > I know I've wanted to be able to do this at times, though again, I've worked > around it and lived, and surely it could be abused. > I'm OK cutting this one back. I think we should only allow throw as an expression if the precedent symbol was '->', i.e. only allows throw as an expression in lambda body and in the body of an expression switch. It seems to cover what we want without making the code too un-java-y. R?mi -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Wed Dec 13 10:25:47 2017 From: forax at univ-mlv.fr (Remi Forax) Date: Wed, 13 Dec 2017 11:25:47 +0100 (CET) Subject: switch with several arguments Message-ID: <1684475503.1194536.1513160747109.JavaMail.zimbra@u-pem.fr> Should we support a switch with several arguments ? By example: for(int i = 1; i < 100; i++) { System.out.println( switch(i % 3, i % 5) { case (0, 0) -> "FizzBuzz"; case (0, _) -> "Fizz"; case (_, 0) -> "Buzz"; default -> i; }); } Apart FizzBuzz, it's very convenient when you want to merge things, take decision depending on more than one value. Note that this example also use the "locals do not need to be effectively final" semantics. R?mi From gavin.bierman at oracle.com Wed Dec 13 13:43:00 2017 From: gavin.bierman at oracle.com (Gavin Bierman) Date: Wed, 13 Dec 2017 13:43:00 +0000 Subject: switch with several arguments In-Reply-To: <1684475503.1194536.1513160747109.JavaMail.zimbra@u-pem.fr> References: <1684475503.1194536.1513160747109.JavaMail.zimbra@u-pem.fr> Message-ID: You?ll not be surprised to hear that we?ve thought about it. It is certainly a very cool extension and makes for some very slick code. Personally I think it should wait until we have thought about tuples more generally (which might well be part of a value types enhancement). Gavin > On 13 Dec 2017, at 10:25, Remi Forax wrote: > > Should we support a switch with several arguments ? > > By example: > for(int i = 1; i < 100; i++) { > System.out.println( > switch(i % 3, i % 5) { > case (0, 0) -> "FizzBuzz"; > case (0, _) -> "Fizz"; > case (_, 0) -> "Buzz"; > default -> i; > }); > } > > Apart FizzBuzz, it's very convenient when you want to merge things, take decision depending on more than one value. > > Note that this example also use the "locals do not need to be effectively final" semantics. > > R?mi From brian.goetz at oracle.com Wed Dec 13 14:38:31 2017 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 13 Dec 2017 09:38:31 -0500 Subject: New JEP: Switch Expressions for the Java Language In-Reply-To: <1340045638.1183461.1513159684203.JavaMail.zimbra@u-pem.fr> References: <1932fb57-fda7-ac1d-57ec-2d062b56766e@oracle.com> <6F1D8934-488E-4643-A006-4D1CC57C70E7@oracle.com> <746568769.2856118.1512932376514.JavaMail.zimbra@u-pem.fr> <27A87FD0-E010-449A-B488-BE120BDCEC3D@oracle.com> <1340045638.1183461.1513159684203.JavaMail.zimbra@u-pem.fr> Message-ID: <61ded91c-433a-30df-9b86-df27dcbb7249@oracle.com> We came to pretty much the same conclusion internally.? Lambdas and switch expressions are where this is needed. On 12/13/2017 5:08 AM, forax at univ-mlv.fr wrote: > > I think we should only allow throw as an expression if the precedent > symbol was '->', i.e. only allows throw as an expression in lambda > body and in the body of an expression switch. > It seems to cover what we want without making the code too un-java-y. From forax at univ-mlv.fr Wed Dec 13 14:45:44 2017 From: forax at univ-mlv.fr (Remi Forax) Date: Wed, 13 Dec 2017 14:45:44 +0000 Subject: switch with several arguments In-Reply-To: References: <1684475503.1194536.1513160747109.JavaMail.zimbra@u-pem.fr> Message-ID: Technically, you do not need tuples/value types because you push the value, matching on several arguments is already something that need to be supported when destructuring because you need to match all the arguments of the result of a call to the destructor. Anyway , at least, we have to check that the meta protocol will allow such extension. R?mi I'm typing this in the movie theater waiting for last star wars, if you do not agree, I will tell you how it's ends. On December 13, 2017 2:43:00 PM GMT+01:00, Gavin Bierman wrote: >You?ll not be surprised to hear that we?ve thought about it. It is >certainly a very cool extension and makes for some very slick code. >Personally I think it should wait until we have thought about tuples >more generally (which might well be part of a value types enhancement). > >Gavin > >> On 13 Dec 2017, at 10:25, Remi Forax wrote: >> >> Should we support a switch with several arguments ? >> >> By example: >> for(int i = 1; i < 100; i++) { >> System.out.println( >> switch(i % 3, i % 5) { >> case (0, 0) -> "FizzBuzz"; >> case (0, _) -> "Fizz"; >> case (_, 0) -> "Buzz"; >> default -> i; >> }); >> } >> >> Apart FizzBuzz, it's very convenient when you want to merge things, >take decision depending on more than one value. >> >> Note that this example also use the "locals do not need to be >effectively final" semantics. >> >> R?mi -- Sent from my Android device with K-9 Mail. Please excuse my brevity. -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Wed Dec 13 18:55:19 2017 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Wed, 13 Dec 2017 19:55:19 +0100 (CET) Subject: New JEP: Switch Expressions for the Java Language In-Reply-To: <61ded91c-433a-30df-9b86-df27dcbb7249@oracle.com> References: <1932fb57-fda7-ac1d-57ec-2d062b56766e@oracle.com> <746568769.2856118.1512932376514.JavaMail.zimbra@u-pem.fr> <27A87FD0-E010-449A-B488-BE120BDCEC3D@oracle.com> <1340045638.1183461.1513159684203.JavaMail.zimbra@u-pem.fr> <61ded91c-433a-30df-9b86-df27dcbb7249@oracle.com> Message-ID: <924884077.1430345.1513191319235.JavaMail.zimbra@u-pem.fr> ----- Mail original ----- > De: "Brian Goetz" > ?: forax at univ-mlv.fr > Cc: "Kevin Bourrillion" , "amber-spec-experts" > Envoy?: Mercredi 13 D?cembre 2017 15:38:31 > Objet: Re: New JEP: Switch Expressions for the Java Language > We came to pretty much the same conclusion internally.? Lambdas and > switch expressions are where this is needed. Ok, cool. R?mi > > On 12/13/2017 5:08 AM, forax at univ-mlv.fr wrote: >> >> I think we should only allow throw as an expression if the precedent >> symbol was '->', i.e. only allows throw as an expression in lambda >> body and in the body of an expression switch. > > It seems to cover what we want without making the code too un-java-y. From john.r.rose at oracle.com Thu Dec 14 00:51:01 2017 From: john.r.rose at oracle.com (John Rose) Date: Wed, 13 Dec 2017 16:51:01 -0800 Subject: Switching on float/double/long In-Reply-To: <1801453083.1035590.1513115572265.JavaMail.zimbra@u-pem.fr> References: <1801453083.1035590.1513115572265.JavaMail.zimbra@u-pem.fr> Message-ID: <917C6484-5AEF-43E2-9D0A-2CA5CBB4671F@oracle.com> Joe's points make perfect sense to me. Because of distinct problems with float, double, and reference operand types, the "==" operator of Java is a poor equivalence relation, so just referring the semantics of switch to op== is IMO a false start for defining switch. Switch-on-string has already broken with that false start, in the case of references, using Object.equals. A coherent way to break from op== on floats is to, also, refer to the closest possible Object.equals method, that on Float (and Double). Joe's proposal in fact appeals to the same standards, that of floatToIntBits. https://docs.oracle.com/javase/7/docs/api/java/lang/Float.html#equals(java.lang.Object) The most fine-grained equality relation that can be defined across all types does not have an API point, but it can be called "substitutability". For references substitutability is simply acmp, or op==(Object,Object). For floats, substitutability is approximated by equality on floatToIntBits, but defined rigorously by equality on floatToRawIntBits, which preserves distinctions among NaNs. Since those distinctions can be observed by code, two distinct NaNs cannot be said to be substitutable for each other. Joe's comparison, and that of Float.equals, is slightly more coarse-grained of an equivalence relation, because all the NaNs are grouped into a single cluster. I wish the designer of Float.equals had not stopped arbitrarily at NaN folding, and used floatToRawIntBits. But, given that history, I think when switch supports floats and doubles, it will use Joe's comparison. As Remi points out, suitable third-party extractors (or value type wrappers) can provide other relations besides Joe's default, either distinguishing NaNs or lumping zeroes. Perhaps even rejecting NaNs, since they aren't equal to themselves, supposedly. But we only get to set the default once. So perhaps we should delay supporting floats directly, until we can put all three or four float matching predicates in front of us and decide which is the default. I see no corresponding reason to delay longs. Instead, I see a pressing need to figure out the correct relation between switch (x) { case (byte)1; } where x might be a long or Long. I don't see a way to delay that decision. Backing up a bit, I prefer to evaluate match semantics in terms of assignment detection, rather than ad hoc equality predicates. If the story is only ad hoc, "if this type then this predicate" I am sure it will have more nasty corners than it needs. If it has an overarching principle, then I am sure it will have nasty corners (as with +0 and NaNs), but only a minimum of them. And the overarching principle I prefer for match is to ask the following polymorphic question: "Could a value just like this case expression have been assigned to that switch variable?" This, IMO, unwinds a lot of otherwise ad hoc special pleading. It does require some ad hoc definition of what "just like this" means, but the rest falls out of prior JLS semantics. Including the vexed questions which will be occurring to you, above, about Long vs. long vs. byte. ? John On Dec 12, 2017, at 1:52 PM, Remi Forax wrote: > > While we could do that, use bits representation for float and double, this is typically the kind of things that a user can also do with a record (a value type record ?) and a deconstructor, so in my opinion, we should not rush to implement this kind of switch given that we will soon provide a general mechanism to implement them outside of the JDK. > > R?mi > > De: "Brian Goetz" > ?: "amber-spec-experts" > Envoy?: Lundi 11 D?cembre 2017 22:25:34 > Objet: Switching on float/double/long > A target of opportunity for the new switch JEP is to fill out the set of types that traditional switches can operate on -- specifically float, double, and long. The reason that we don't support these now is mostly an accident of history; the `tableswitch` and `lookupswitch` opcodes are int-based, so the compiler doesn't have a convenient target for translating these. As you've seen from the recent notes on switch translation, we're working towards using indy more broadly as a translation target for most switch constructs. This makes it far easier to bust the limitations on switch argument types, and so this has been listed as a target of opportunity in the JEP (for both statement and expression switches.) > > Our resident floating-point expert, Joe Darcy, offers the following additional thoughts on the subject: > > -- Begin forwarded message > > Per a recent request from Brian, I've written a few thoughts about switching on floating-point values. > > To address some common misunderstandings of floating-point, while it is often recommended to *not* compare floating-point values for equality, it is perfectly well-defined to do such comparisons, it just might not do what you want > > For example, instead of > > // Infinite loop since sum stored in d never exactly equals 1.0, doh! > while(d != 1.0)\u000B > d += 0.1; > > use either > > // Counted loop > for(int i = 0; i < 10; i++)\u000B > d += 0.1; > > or > > // Stop when numerical threshold is met > while(d <= 1.0)\u000B > d += 0.1; > > depending on the semantics the loop is trying to capture. > > I've attached a slide from my JVMLS talk this year to help illustrate the semantic modeling going in in IEEE 754 floating-point. Each of the 232 possible bit patterns of a float is some floating-point value, likewise for the 264 possible bit patterns of a double. However, from a Java language or JVM perspective, there are not 232 or 264 distinct values we need or want to distinguish in most cases. In particular, we almost always want to treat all bit patterns which encode a NaN as a single conceptual NaN. Another wrinkle concerns zero: IEEE 754 has both a positive zero and a negative zero. Why are there *two* zeros? Because there are two infinities. The signed infinities and distinguished by divide (1.0/0.0 => +infinity, 1.0/-0.0 => -infinity) and by various library functions. > > So we want to: > > * Allow every distinct finite nonzero floating-point value to be the case of a switch. > * Allow -0.0 and +0.0 to be treated separately. > * Allow -infinity and +infinity to be treated separately. > * Collapse all NaN representation as a single value. > > For the "Rounding" mapping in the diagram which goes from the extended real numbers to floating-point data, there is a nonempty segment of the real number line which maps to a given representable floating-point number. For example, besides the string "1.0" mapping exactly to the reprentable floating-point value 1.0, there is a region slightly small than 1 (0.99999999999999999999...) which will round up to 1.0 and a region slightly larger than 1 (1.000000000000000001...) which will round down to 1 from decimal -> binary conversion. This would need to be factored into any distinctiveness requirements for the different arms of the switch. In other words > > case 1.000000000000000001: > .... > case 0.99999999999999999999 > ... > > would need to be rejected just as > > case 0: > .... > case 00: > > is rejected. > > In terms of JDK 9 structures and operations, the following transformation of a float switch has what I think are reasonable semantics: > > Replace each float case label y in the source with an int label resulting from floatToIntBits(y). Note that floatToIntBits is used for the mapping rather than floatToRawIntBits since we want NaNs to be grouped together. > > Instead of switching on float value x, switch on floatToIntBits(x). > > HTH, > > -Joe -------------- next part -------------- An HTML attachment was scrubbed... URL: From paul.sandoz at oracle.com Thu Dec 14 01:44:08 2017 From: paul.sandoz at oracle.com (Paul Sandoz) Date: Wed, 13 Dec 2017 17:44:08 -0800 Subject: Switching on float/double/long In-Reply-To: <917C6484-5AEF-43E2-9D0A-2CA5CBB4671F@oracle.com> References: <1801453083.1035590.1513115572265.JavaMail.zimbra@u-pem.fr> <917C6484-5AEF-43E2-9D0A-2CA5CBB4671F@oracle.com> Message-ID: <27E834CA-D0AA-4279-B0AB-32249808C907@oracle.com> Recently i was mildly annoyed to discover that Float/DoubleBuffer provide another variant of equality different to that of Float.equals/Arrays.equals and ==, specifically: * This method considers two float elements {@code a} and {@code b} * to be equal if * {@code (a == b) || (Float.isNaN(a) && Float.isNaN(b))}. * The values {@code -0.0} and {@code +0.0} are considered to be * equal, unlike {@link Float#equals(Object)}. The vectorized implementations for float/double comparison/equality leverage the equivalent of floatToRawIntBits and on a mismatch have to check if it was caused by NaNs and if continue the search. The equivalent vectorized implementation for buffers (in progress) needs to do the same for NaNs and +0/-0. I can imagine bit-wise comparison is problematic since IIUC the actually bit pattern of a NaN value can, in a platform specific manner, change depending on how it?s operated on. Paul. > On 13 Dec 2017, at 16:51, John Rose wrote: > > Joe's points make perfect sense to me. > > Because of distinct problems with float, double, and reference operand > types, the "==" operator of Java is a poor equivalence relation, so just > referring the semantics of switch to op== is IMO a false start for defining > switch. Switch-on-string has already broken with that false start, in the > case of references, using Object.equals. A coherent way to break from > op== on floats is to, also, refer to the closest possible Object.equals > method, that on Float (and Double). Joe's proposal in fact appeals to > the same standards, that of floatToIntBits. > > https://docs.oracle.com/javase/7/docs/api/java/lang/Float.html#equals(java.lang.Object) > > The most fine-grained equality relation that can be defined across all > types does not have an API point, but it can be called "substitutability". > For references substitutability is simply acmp, or op==(Object,Object). > For floats, substitutability is approximated by equality on floatToIntBits, > but defined rigorously by equality on floatToRawIntBits, which preserves > distinctions among NaNs. Since those distinctions can be observed by > code, two distinct NaNs cannot be said to be substitutable for each > other. > > Joe's comparison, and that of Float.equals, is slightly more coarse-grained > of an equivalence relation, because all the NaNs are grouped into a single > cluster. I wish the designer of Float.equals had not stopped arbitrarily at > NaN folding, and used floatToRawIntBits. But, given that history, I think > when switch supports floats and doubles, it will use Joe's comparison. > > As Remi points out, suitable third-party extractors (or value type wrappers) > can provide other relations besides Joe's default, either distinguishing > NaNs or lumping zeroes. Perhaps even rejecting NaNs, since they aren't > equal to themselves, supposedly. > > But we only get to set the default once. So perhaps we should delay > supporting floats directly, until we can put all three or four float > matching predicates in front of us and decide which is the default. > > I see no corresponding reason to delay longs. Instead, I see a pressing > need to figure out the correct relation between switch (x) { case (byte)1; } > where x might be a long or Long. I don't see a way to delay that decision. > > Backing up a bit, I prefer to evaluate match semantics in terms of assignment > detection, rather than ad hoc equality predicates. If the story is only ad > hoc, "if this type then this predicate" I am sure it will have more nasty > corners than it needs. If it has an overarching principle, then I am sure > it will have nasty corners (as with +0 and NaNs), but only a minimum > of them. And the overarching principle I prefer for match is to ask the > following polymorphic question: "Could a value just like this case > expression have been assigned to that switch variable?" This, IMO, > unwinds a lot of otherwise ad hoc special pleading. It does require > some ad hoc definition of what "just like this" means, but the rest falls > out of prior JLS semantics. Including the vexed questions which will > be occurring to you, above, about Long vs. long vs. byte. > > ? John > > On Dec 12, 2017, at 1:52 PM, Remi Forax wrote: >> >> While we could do that, use bits representation for float and double, this is typically the kind of things that a user can also do with a record (a value type record ?) and a deconstructor, so in my opinion, we should not rush to implement this kind of switch given that we will soon provide a general mechanism to implement them outside of the JDK. >> >> R?mi >> >> De: "Brian Goetz" >> ?: "amber-spec-experts" >> Envoy?: Lundi 11 D?cembre 2017 22:25:34 >> Objet: Switching on float/double/long >> A target of opportunity for the new switch JEP is to fill out the set of types that traditional switches can operate on -- specifically float, double, and long. The reason that we don't support these now is mostly an accident of history; the `tableswitch` and `lookupswitch` opcodes are int-based, so the compiler doesn't have a convenient target for translating these. As you've seen from the recent notes on switch translation, we're working towards using indy more broadly as a translation target for most switch constructs. This makes it far easier to bust the limitations on switch argument types, and so this has been listed as a target of opportunity in the JEP (for both statement and expression switches.) >> >> Our resident floating-point expert, Joe Darcy, offers the following additional thoughts on the subject: >> >> -- Begin forwarded message >> >> Per a recent request from Brian, I've written a few thoughts about switching on floating-point values. >> >> To address some common misunderstandings of floating-point, while it is often recommended to *not* compare floating-point values for equality, it is perfectly well-defined to do such comparisons, it just might not do what you want >> >> For example, instead of >> >> // Infinite loop since sum stored in d never exactly equals 1.0, doh! >> while(d != 1.0)\u000B >> d += 0.1; >> >> use either >> >> // Counted loop >> for(int i = 0; i < 10; i++)\u000B >> d += 0.1; >> >> or >> >> // Stop when numerical threshold is met >> while(d <= 1.0)\u000B >> d += 0.1; >> >> depending on the semantics the loop is trying to capture. >> >> I've attached a slide from my JVMLS talk this year to help illustrate the semantic modeling going in in IEEE 754 floating-point. Each of the 232 possible bit patterns of a float is some floating-point value, likewise for the 264 possible bit patterns of a double. However, from a Java language or JVM perspective, there are not 232 or 264 distinct values we need or want to distinguish in most cases. In particular, we almost always want to treat all bit patterns which encode a NaN as a single conceptual NaN. Another wrinkle concerns zero: IEEE 754 has both a positive zero and a negative zero. Why are there *two* zeros? Because there are two infinities. The signed infinities and distinguished by divide (1.0/0.0 => +infinity, 1.0/-0.0 => -infinity) and by various library functions. >> >> So we want to: >> >> * Allow every distinct finite nonzero floating-point value to be the case of a switch. >> * Allow -0.0 and +0.0 to be treated separately. >> * Allow -infinity and +infinity to be treated separately. >> * Collapse all NaN representation as a single value. >> >> For the "Rounding" mapping in the diagram which goes from the extended real numbers to floating-point data, there is a nonempty segment of the real number line which maps to a given representable floating-point number. For example, besides the string "1.0" mapping exactly to the reprentable floating-point value 1.0, there is a region slightly small than 1 (0.99999999999999999999...) which will round up to 1.0 and a region slightly larger than 1 (1.000000000000000001...) which will round down to 1 from decimal -> binary conversion. This would need to be factored into any distinctiveness requirements for the different arms of the switch. In other words >> >> case 1.000000000000000001: >> .... >> case 0.99999999999999999999 >> ... >> >> would need to be rejected just as >> >> case 0: >> .... >> case 00: >> >> is rejected. >> >> In terms of JDK 9 structures and operations, the following transformation of a float switch has what I think are reasonable semantics: >> >> Replace each float case label y in the source with an int label resulting from floatToIntBits(y). Note that floatToIntBits is used for the mapping rather than floatToRawIntBits since we want NaNs to be grouped together. >> >> Instead of switching on float value x, switch on floatToIntBits(x). >> >> HTH, >> >> -Joe > From kevinb at google.com Thu Dec 14 16:09:02 2017 From: kevinb at google.com (Kevin Bourrillion) Date: Thu, 14 Dec 2017 08:09:02 -0800 Subject: Switching on float/double/long In-Reply-To: <27E834CA-D0AA-4279-B0AB-32249808C907@oracle.com> References: <1801453083.1035590.1513115572265.JavaMail.zimbra@u-pem.fr> <917C6484-5AEF-43E2-9D0A-2CA5CBB4671F@oracle.com> <27E834CA-D0AA-4279-B0AB-32249808C907@oracle.com> Message-ID: Switch on long: sure. Switch on float/double: why? As someone who puts nontrivial effort into trying to get developers in my company to *stop* ever depending on exact equality of floats and doubles, the only effect of this change will be to give me one additional thing to tell people not to do. We already have ==, !=, equals, assertEquals, and using as a key in that list, so in a sense, "what's one more?". But -- were any actual advantages to doing this mentioned in this thread? I don't see them. It seems like the thread skipped right over that part? If I had to guess, I'm guessing it might have something to do with the idea that Float/Double will automatically get supported by pattern-matching and there's nothing we can do about that. Is it something like that? On Wed, Dec 13, 2017 at 5:44 PM, Paul Sandoz wrote: > Recently i was mildly annoyed to discover that Float/DoubleBuffer provide > another variant of equality different to that of Float.equals/Arrays.equals > and ==, specifically: > > * This method considers two float elements {@code a} and {@code b} > * to be equal if > * {@code (a == b) || (Float.isNaN(a) && Float.isNaN(b))}. > * The values {@code -0.0} and {@code +0.0} are considered to be > * equal, unlike {@link Float#equals(Object)}. > > The vectorized implementations for float/double comparison/equality > leverage the equivalent of floatToRawIntBits and on a mismatch have to > check if it was caused by NaNs and if continue the search. The equivalent > vectorized implementation for buffers (in progress) needs to do the same > for NaNs and +0/-0. > > I can imagine bit-wise comparison is problematic since IIUC the actually > bit pattern of a NaN value can, in a platform specific manner, change > depending on how it?s operated on. > > Paul. > > > On 13 Dec 2017, at 16:51, John Rose wrote: > > > > Joe's points make perfect sense to me. > > > > Because of distinct problems with float, double, and reference operand > > types, the "==" operator of Java is a poor equivalence relation, so just > > referring the semantics of switch to op== is IMO a false start for > defining > > switch. Switch-on-string has already broken with that false start, in > the > > case of references, using Object.equals. A coherent way to break from > > op== on floats is to, also, refer to the closest possible Object.equals > > method, that on Float (and Double). Joe's proposal in fact appeals to > > the same standards, that of floatToIntBits. > > > > https://docs.oracle.com/javase/7/docs/api/java/lang/ > Float.html#equals(java.lang.Object) > > > > The most fine-grained equality relation that can be defined across all > > types does not have an API point, but it can be called > "substitutability". > > For references substitutability is simply acmp, or op==(Object,Object). > > For floats, substitutability is approximated by equality on > floatToIntBits, > > but defined rigorously by equality on floatToRawIntBits, which preserves > > distinctions among NaNs. Since those distinctions can be observed by > > code, two distinct NaNs cannot be said to be substitutable for each > > other. > > > > Joe's comparison, and that of Float.equals, is slightly more > coarse-grained > > of an equivalence relation, because all the NaNs are grouped into a > single > > cluster. I wish the designer of Float.equals had not stopped > arbitrarily at > > NaN folding, and used floatToRawIntBits. But, given that history, I > think > > when switch supports floats and doubles, it will use Joe's comparison. > > > > As Remi points out, suitable third-party extractors (or value type > wrappers) > > can provide other relations besides Joe's default, either distinguishing > > NaNs or lumping zeroes. Perhaps even rejecting NaNs, since they aren't > > equal to themselves, supposedly. > > > > But we only get to set the default once. So perhaps we should delay > > supporting floats directly, until we can put all three or four float > > matching predicates in front of us and decide which is the default. > > > > I see no corresponding reason to delay longs. Instead, I see a pressing > > need to figure out the correct relation between switch (x) { case > (byte)1; } > > where x might be a long or Long. I don't see a way to delay that > decision. > > > > Backing up a bit, I prefer to evaluate match semantics in terms of > assignment > > detection, rather than ad hoc equality predicates. If the story is only > ad > > hoc, "if this type then this predicate" I am sure it will have more nasty > > corners than it needs. If it has an overarching principle, then I am > sure > > it will have nasty corners (as with +0 and NaNs), but only a minimum > > of them. And the overarching principle I prefer for match is to ask the > > following polymorphic question: "Could a value just like this case > > expression have been assigned to that switch variable?" This, IMO, > > unwinds a lot of otherwise ad hoc special pleading. It does require > > some ad hoc definition of what "just like this" means, but the rest falls > > out of prior JLS semantics. Including the vexed questions which will > > be occurring to you, above, about Long vs. long vs. byte. > > > > ? John > > > > On Dec 12, 2017, at 1:52 PM, Remi Forax wrote: > >> > >> While we could do that, use bits representation for float and double, > this is typically the kind of things that a user can also do with a record > (a value type record ?) and a deconstructor, so in my opinion, we should > not rush to implement this kind of switch given that we will soon provide a > general mechanism to implement them outside of the JDK. > >> > >> R?mi > >> > >> De: "Brian Goetz" > >> ?: "amber-spec-experts" > >> Envoy?: Lundi 11 D?cembre 2017 22:25:34 > >> Objet: Switching on float/double/long > >> A target of opportunity for the new switch JEP is to fill out the set > of types that traditional switches can operate on -- specifically float, > double, and long. The reason that we don't support these now is mostly an > accident of history; the `tableswitch` and `lookupswitch` opcodes are > int-based, so the compiler doesn't have a convenient target for translating > these. As you've seen from the recent notes on switch translation, we're > working towards using indy more broadly as a translation target for most > switch constructs. This makes it far easier to bust the limitations on > switch argument types, and so this has been listed as a target of > opportunity in the JEP (for both statement and expression switches.) > >> > >> Our resident floating-point expert, Joe Darcy, offers the following > additional thoughts on the subject: > >> > >> -- Begin forwarded message > >> > >> Per a recent request from Brian, I've written a few thoughts about > switching on floating-point values. > >> > >> To address some common misunderstandings of floating-point, while it is > often recommended to *not* compare floating-point values for equality, it > is perfectly well-defined to do such comparisons, it just might not do what > you want > >> > >> For example, instead of > >> > >> // Infinite loop since sum stored in d never exactly equals 1.0, > doh! > >> while(d != 1.0)\u000B > >> d += 0.1; > >> > >> use either > >> > >> // Counted loop > >> for(int i = 0; i < 10; i++)\u000B > >> d += 0.1; > >> > >> or > >> > >> // Stop when numerical threshold is met > >> while(d <= 1.0)\u000B > >> d += 0.1; > >> > >> depending on the semantics the loop is trying to capture. > >> > >> I've attached a slide from my JVMLS talk this year to help illustrate > the semantic modeling going in in IEEE 754 floating-point. Each of the 232 > possible bit patterns of a float is some floating-point value, likewise for > the 264 possible bit patterns of a double. However, from a Java language or > JVM perspective, there are not 232 or 264 distinct values we need or want > to distinguish in most cases. In particular, we almost always want to treat > all bit patterns which encode a NaN as a single conceptual NaN. Another > wrinkle concerns zero: IEEE 754 has both a positive zero and a negative > zero. Why are there *two* zeros? Because there are two infinities. The > signed infinities and distinguished by divide (1.0/0.0 => +infinity, > 1.0/-0.0 => -infinity) and by various library functions. > >> > >> So we want to: > >> > >> * Allow every distinct finite nonzero floating-point value to be > the case of a switch. > >> * Allow -0.0 and +0.0 to be treated separately. > >> * Allow -infinity and +infinity to be treated separately. > >> * Collapse all NaN representation as a single value. > >> > >> For the "Rounding" mapping in the diagram which goes from the extended > real numbers to floating-point data, there is a nonempty segment of the > real number line which maps to a given representable floating-point number. > For example, besides the string "1.0" mapping exactly to the reprentable > floating-point value 1.0, there is a region slightly small than 1 > (0.99999999999999999999...) which will round up to 1.0 and a region > slightly larger than 1 (1.000000000000000001...) which will round down to 1 > from decimal -> binary conversion. This would need to be factored into any > distinctiveness requirements for the different arms of the switch. In other > words > >> > >> case 1.000000000000000001: > >> .... > >> case 0.99999999999999999999 > >> ... > >> > >> would need to be rejected just as > >> > >> case 0: > >> .... > >> case 00: > >> > >> is rejected. > >> > >> In terms of JDK 9 structures and operations, the following > transformation of a float switch has what I think are reasonable semantics: > >> > >> Replace each float case label y in the source with an int label > resulting from floatToIntBits(y). Note that floatToIntBits is used for the > mapping rather than floatToRawIntBits since we want NaNs to be grouped > together. > >> > >> Instead of switching on float value x, switch on floatToIntBits(x). > >> > >> HTH, > >> > >> -Joe > > > > -- Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb at google.com -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Thu Dec 14 16:25:58 2017 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 14 Dec 2017 11:25:58 -0500 Subject: Switching on float/double/long In-Reply-To: References: <1801453083.1035590.1513115572265.JavaMail.zimbra@u-pem.fr> <917C6484-5AEF-43E2-9D0A-2CA5CBB4671F@oracle.com> <27E834CA-D0AA-4279-B0AB-32249808C907@oracle.com> Message-ID: <1c805803-941c-57e8-3dde-4c71a74654b8@oracle.com> Part of the motivation is removing arbitrary limitations now that the translation machinery has caught up.? But more of it is, indeed, having to do with pattern matching.? And while you might think "who's going to match on floats", the reality is that this actually will happen more than you think, because of _nested patterns_.? (In general, if you see us talking about some sort of pattern matching corner case that you can't imagine using, it's because of nesting; this is where all the weirdness surrounding null came from.) A pattern `Point(int x, int y)` looks like a single pattern, but really it's a nested pattern; this unrolls to: ??? Point(\alpha, \beta) where \alpha matches int x && \beta matches int y So while the Point components above look like a declaration, they are really just patterns.? And a pattern like `Point(var x, var y)` really means "look at the declared deconstructors of Point, do overload selection, and infer the types for `var` from that, and then treat it as a pattern with nested type-test patterns." So, suppose we have a `Complex` type whose components are doubles. You might well want to match on something like: ??? case Complex(var re, var im) where im == 0.0d? // keep it real, man While this seems perfectly fine, the simplest and most natural way to define this means that we must be willing to match against the pattern "double im".? Once we've defined the semantics of matching against float types and constants, why would we restrict them from the constructs that support pattern matching? Should we tell people "sorry, you can't match against classes like Complex, because floating point is hard"? Sure, we could add all kinds of special cases to treat nested patterns in some non-uniform way to avoid having to define the semantics for floats, but that's more complicated and less expressive. So the motivation is not so much "switching on float is the new hotness, everyone should do it", but "matching floats makes sense, and defining switching in terms of matching makes sense." All that said, we could still special-case it by limiting the set of types that switch can apply to, so the pattern matching machinery on floats is there but switching is not.? But the burden should probably get flipped; it's not "why should we support it", but "why should we take steps to prevent it", since it falls out naturally via composition. On 12/14/2017 11:09 AM, Kevin Bourrillion wrote: > Switch on long: sure. > > Switch on float/double: why? > > As someone who puts nontrivial effort into trying to get developers in > my company to /stop/?ever depending on exact equality of floats and > doubles, the only effect of this change will be to give me one > additional thing to tell people not to do. > > We already have ==, !=, equals, assertEquals, and using as a key in > that list, so in a sense, "what's one more?". But -- were any actual > advantages to doing this mentioned in this thread? I don't see them. > It seems like the thread skipped right over that part? > > If I had to guess, I'm guessing it might have something to do with the > idea that Float/Double will automatically get supported by > pattern-matching and there's nothing we can do about that. Is it > something like that? > > > > > > On Wed, Dec 13, 2017 at 5:44 PM, Paul Sandoz > wrote: > > Recently i was mildly annoyed to discover that Float/DoubleBuffer > provide another variant of equality different to that of > Float.equals/Arrays.equals and ==, specifically: > > *? ?This method considers two float elements {@code a} and {@code b} > *? ?to be equal if > *? ?{@code (a == b) || (Float.isNaN(a) && Float.isNaN(b))}. > *? ?The values {@code -0.0} and {@code +0.0} are considered to be > *? ?equal, unlike {@link Float#equals(Object)}. > > The vectorized implementations for float/double > comparison/equality leverage the equivalent of floatToRawIntBits > and on a mismatch have to check if it was caused by NaNs and if > continue the search. The equivalent vectorized implementation for > buffers (in progress) needs to do the same for NaNs and +0/-0. > > I can imagine bit-wise comparison is problematic since IIUC the > actually bit pattern of a NaN value can, in a platform specific > manner, change depending on how it?s operated on. > > Paul. > > > On 13 Dec 2017, at 16:51, John Rose > wrote: > > > > Joe's points make perfect sense to me. > > > > Because of distinct problems with float, double, and reference > operand > > types, the "==" operator of Java is a poor equivalence relation, > so just > > referring the semantics of switch to op== is IMO a false start > for defining > > switch.? Switch-on-string has already broken with that false > start, in the > > case of references, using Object.equals.? A coherent way to > break from > > op== on floats is to, also, refer to the closest possible > Object.equals > > method, that on Float (and Double).? Joe's proposal in fact > appeals to > > the same standards, that of floatToIntBits. > > > > > https://docs.oracle.com/javase/7/docs/api/java/lang/Float.html#equals(java.lang.Object) > > > > > The most fine-grained equality relation that can be defined > across all > > types does not have an API point, but it can be called > "substitutability". > > For references substitutability is simply acmp, or > op==(Object,Object). > > For floats, substitutability is approximated by equality on > floatToIntBits, > > but defined rigorously by equality on floatToRawIntBits, which > preserves > > distinctions among NaNs.? Since those distinctions can be > observed by > > code, two distinct NaNs cannot be said to be substitutable for each > > other. > > > > Joe's comparison, and that of Float.equals, is slightly more > coarse-grained > > of an equivalence relation, because all the NaNs are grouped > into a single > > cluster.? I wish the designer of Float.equals had not stopped > arbitrarily at > > NaN folding, and used floatToRawIntBits.? But, given that > history, I think > > when switch supports floats and doubles, it will use Joe's > comparison. > > > > As Remi points out, suitable third-party extractors (or value > type wrappers) > > can provide other relations besides Joe's default, either > distinguishing > > NaNs or lumping zeroes.? Perhaps even rejecting NaNs, since they > aren't > > equal to themselves, supposedly. > > > > But we only get to set the default once.? So perhaps we should delay > > supporting floats directly, until we can put all three or four float > > matching predicates in front of us and decide which is the default. > > > > I see no corresponding reason to delay longs. Instead, I see a > pressing > > need to figure out the correct relation between switch (x) { > case (byte)1; } > > where x might be a long or Long.? I don't see a way to delay > that decision. > > > > Backing up a bit, I prefer to evaluate match semantics in terms > of assignment > > detection, rather than ad hoc equality predicates. If the story > is only ad > > hoc, "if this type then this predicate" I am sure it will have > more nasty > > corners than it needs.? If it has an overarching principle, then > I am sure > > it will have nasty corners (as with +0 and NaNs), but only a minimum > > of them.? And the overarching principle I prefer for match is to > ask the > > following polymorphic question:? "Could a value just like this case > > expression have been assigned to that switch variable?"? This, IMO, > > unwinds a lot of otherwise ad hoc special pleading.? It does require > > some ad hoc definition of what "just like this" means, but the > rest falls > > out of prior JLS semantics.? Including the vexed questions which > will > > be occurring to you, above, about Long vs. long vs. byte. > > > > ? John > > > > On Dec 12, 2017, at 1:52 PM, Remi Forax > wrote: > >> > >> While we could do that, use bits representation for float and > double, this is typically the kind of things that a user can also > do with a record (a value type record ?) and a deconstructor, so > in my opinion, we should not rush to implement this kind of switch > given that we will soon provide a general mechanism to implement > them outside of the JDK. > >> > >> R?mi > >> > >> De: "Brian Goetz" > > >> ?: "amber-spec-experts" > > >> Envoy?: Lundi 11 D?cembre 2017 22:25:34 > >> Objet: Switching on float/double/long > >> A target of opportunity for the new switch JEP is to fill out > the set of types that traditional switches can operate on -- > specifically float, double, and long.? The reason that we don't > support these now is mostly an accident of history; the > `tableswitch` and `lookupswitch` opcodes are int-based, so the > compiler doesn't have a convenient target for translating these. > As you've seen from the recent notes on switch translation, we're > working towards using indy more broadly as a translation target > for most switch constructs.? This makes it far easier to bust the > limitations on switch argument types, and so this has been listed > as a target of opportunity in the JEP (for both statement and > expression switches.) > >> > >> Our resident floating-point expert, Joe Darcy, offers the > following additional thoughts on the subject: > >> > >> -- Begin forwarded message > >> > >> Per a recent request from Brian, I've written a few thoughts > about switching on floating-point values. > >> > >> To address some common misunderstandings of floating-point, > while it is often recommended to *not* compare floating-point > values for equality, it is perfectly well-defined to do such > comparisons, it just might not do what you want > >> > >> For example, instead of > >> > >>? ? ?// Infinite loop since sum stored in d never exactly equals > 1.0, doh! > >>? ? ?while(d != 1.0)\u000B > >>? ? ? ? ?d += 0.1; > >> > >> use either > >> > >>? ? ?// Counted loop > >>? ? ?for(int i = 0; i < 10; i++)\u000B > >>? ? ? ? ?d += 0.1; > >> > >> or > >> > >>? ? ?// Stop when numerical threshold is met > >>? ? ?while(d <= 1.0)\u000B > >>? ? ? ? ?d += 0.1; > >> > >> depending on the semantics the loop is trying to capture. > >> > >> I've attached a slide from my JVMLS talk this year to help > illustrate the semantic modeling going in in IEEE 754 > floating-point. Each of the 232 possible bit patterns of a float > is some floating-point value, likewise for the 264 possible bit > patterns of a double. However, from a Java language or JVM > perspective, there are not 232 or 264 distinct values we need or > want to distinguish in most cases. In particular, we almost always > want to treat all bit patterns which encode a NaN as a single > conceptual NaN. Another wrinkle concerns zero: IEEE 754 has both a > positive zero and a negative zero. Why are there *two* zeros? > Because there are two infinities.? The signed infinities and > distinguished by divide (1.0/0.0 => +infinity, 1.0/-0.0 => > -infinity) and by various library functions. > >> > >> So we want to: > >> > >>? ? ?* Allow every distinct finite nonzero floating-point value > to be? the case of a switch. > >>? ? ?* Allow -0.0 and +0.0 to be treated separately. > >>? ? ?* Allow -infinity and +infinity to be treated separately. > >>? ? ?* Collapse all NaN representation as a single value. > >> > >> For the "Rounding" mapping in the diagram which goes from the > extended real numbers to floating-point data, there is a nonempty > segment of the real number line which maps to a given > representable floating-point number. For example, besides the > string "1.0" mapping exactly to the reprentable floating-point > value 1.0, there is a region slightly small than 1 > (0.99999999999999999999...) which will round up to 1.0 and a > region slightly larger than 1 (1.000000000000000001...) which will > round down to 1 from decimal -> binary conversion. This would need > to be factored into any distinctiveness requirements for the > different arms of the switch. In other words > >> > >>? ? ?case 1.000000000000000001: > >>? ? ?.... > >>? ? ?case 0.99999999999999999999 > >>? ? ?... > >> > >> would need to be rejected just as > >> > >>? ? ?case 0: > >>? ? ?.... > >>? ? ?case 00: > >> > >> is rejected. > >> > >> In terms of JDK 9 structures and operations, the following > transformation of a float switch has what I think are reasonable > semantics: > >> > >>? ? ?Replace each float case label y in the source with an int > label resulting from floatToIntBits(y). Note that floatToIntBits > is used for the mapping rather than floatToRawIntBits since we > want NaNs to be grouped together. > >> > >>? ? ?Instead of switching on float value x, switch on > floatToIntBits(x). > >> > >> HTH, > >> > >> -Joe > > > > > > > -- > Kevin Bourrillion?|?Java Librarian |?Google, Inc.?|kevinb at google.com > -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Thu Dec 14 19:39:56 2017 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 14 Dec 2017 14:39:56 -0500 Subject: default branch placement in switch In-Reply-To: References: <4740da81-8d99-645f-fa22-f587b6433278@oracle.com> Message-ID: Here's where I think we should come down on this. First, we should appeal to the general rule about pattern ordering; if pattern X dominates Y (that is, everything that matches Y also matches X), X can't come before Y. This gives us: ?- "case null" must precede any non-constant case label ?- "default" must come at the end. Now, we carve out special dispensation for existing switches: ?- For switches whose static argument type is one of the legacy switch types (primitives, boxes, string, enum) *and* which have only constant case labels (and "default"), we relax the above rule regarding default. On 12/11/2017 2:15 PM, Brian Goetz wrote: > The middle is surely awful.? Though in the JDK, we have a fair number > of uses where default is the _first_ case, which isn't unreasonable, > and some might argue is even clearer in some cases. > > The reason to tread lightly on forcing reorganization of existing > switches is that it is allowable to fall into *and out of* the default > case.? So if someone has: > > ??? switch (x) { > ??????? default:? S; ? // fall through > ??????? case COMMON: T; break; > ??????? case UNCOMMON: U; break; > ??? } > > then eventually getting to an error when default is not last for > "legacy" switches (those where all labels are type-restating > constants) means some uncomfortable refactoring just to "make the > compiler happy."? So while I agree on warnings, I'm not sure if we can > ever get to error in all cases without picking some fights with users. > > > > On 12/11/2017 1:16 PM, Kevin Bourrillion wrote: >> On Fri, Nov 3, 2017 at 5:25 PM, Brian Goetz > > wrote: >> >> >> or plan to eventually get to a place where default always comes >> last, even for "int" switches. If we want to get to the latter, >> we should start warning on this construct now. >> >> >> I favor starting to warn and eventually forbidding default in any >> position but last for all constructs that have it. >> >> A switch with the default in the middle is extremely weird and >> confusing. If I'm reading code to understand what happens when i == >> 3, and I read as far as >> >> switch (i) { >> ? case 1: >> ? ? justOneStuff(); break(); >> ? case 2: >> ? ? justTwoStuff(); break(); >> ? default: >> >> ... then I immediately assume that this must be where execution is >> continuing. Worse, even if I do notice that there are more case >> labels to follow, and I resume searching for a `case 3:`, then when I >> don't find one I now risk making /another/?error and forgetting to >> jump /back/ to the default. >> >> This is kind of insane. At first I was less worried because I thought >> "surely no one is actually doing this"... then I browsed our >> codebase.... yikes. >> >> We should at least strongly consider this. >> >> >> >> On 11/3/2017 5:10 PM, Tagir Valeev wrote: >> >> Hello! >> >> Currently the default branch can be placed in any place >> inside the >> switch operator, e.g. like this: >> >> switch(i) { >> case 1: System.out.println("one");break; >> default: System.out.println("other");break; >> case 2: System.out.println("two");break; >> } >> >> In this case behavior does not change on the order of case >> blocks. >> However in pattern matching the order of cases usually >> matters: if >> some pattern matches, this means that the subsequent patterns >> will not >> be checked. Does this mean that with pattern matching the default >> branch makes all the subsequent case blocks unreachable? Or >> default >> can still be located anywhere and is checked only after any other >> pattern? >> >> With best regards, >> Tagir Valeev >> >> >> >> >> >> -- >> Kevin Bourrillion?|?Java Librarian |?Google, Inc.?|kevinb at google.com >> > -------------- next part -------------- An HTML attachment was scrubbed... URL: From guy.steele at oracle.com Thu Dec 14 19:42:06 2017 From: guy.steele at oracle.com (Guy Steele) Date: Thu, 14 Dec 2017 14:42:06 -0500 Subject: default branch placement in switch In-Reply-To: References: <4740da81-8d99-645f-fa22-f587b6433278@oracle.com> Message-ID: <2518601F-A2C0-4B7E-BF66-5584A453FF9B@oracle.com> I think this is the right approach. An alternative that should at least be considered would be to further simplify the statement of the exception by eliminating the mention of legacy types: - For switches which have only constant case labels (and "default"), we relax the above rule regarding default. > On Dec 14, 2017, at 2:39 PM, Brian Goetz wrote: > > Here's where I think we should come down on this. > > First, we should appeal to the general rule about pattern ordering; if pattern X dominates Y (that is, everything that matches Y also matches X), X can't come before Y. > > This gives us: > - "case null" must precede any non-constant case label > - "default" must come at the end. > > Now, we carve out special dispensation for existing switches: > - For switches whose static argument type is one of the legacy switch types (primitives, boxes, string, enum) *and* which have only constant case labels (and "default"), we relax the above rule regarding default. > > > > On 12/11/2017 2:15 PM, Brian Goetz wrote: >> The middle is surely awful. Though in the JDK, we have a fair number of uses where default is the _first_ case, which isn't unreasonable, and some might argue is even clearer in some cases. >> >> The reason to tread lightly on forcing reorganization of existing switches is that it is allowable to fall into *and out of* the default case. So if someone has: >> >> switch (x) { >> default: S; // fall through >> case COMMON: T; break; >> case UNCOMMON: U; break; >> } >> >> then eventually getting to an error when default is not last for "legacy" switches (those where all labels are type-restating constants) means some uncomfortable refactoring just to "make the compiler happy." So while I agree on warnings, I'm not sure if we can ever get to error in all cases without picking some fights with users. >> >> >> >> On 12/11/2017 1:16 PM, Kevin Bourrillion wrote: >>> On Fri, Nov 3, 2017 at 5:25 PM, Brian Goetz > wrote: >>> >>> or plan to eventually get to a place where default always comes last, even for "int" switches. If we want to get to the latter, we should start warning on this construct now. >>> >>> I favor starting to warn and eventually forbidding default in any position but last for all constructs that have it. >>> >>> A switch with the default in the middle is extremely weird and confusing. If I'm reading code to understand what happens when i == 3, and I read as far as >>> >>> switch (i) { >>> case 1: >>> justOneStuff(); break(); >>> case 2: >>> justTwoStuff(); break(); >>> default: >>> >>> ... then I immediately assume that this must be where execution is continuing. Worse, even if I do notice that there are more case labels to follow, and I resume searching for a `case 3:`, then when I don't find one I now risk making another error and forgetting to jump back to the default. >>> >>> This is kind of insane. At first I was less worried because I thought "surely no one is actually doing this"... then I browsed our codebase.... yikes. >>> >>> We should at least strongly consider this. >>> >>> >>> >>> On 11/3/2017 5:10 PM, Tagir Valeev wrote: >>> Hello! >>> >>> Currently the default branch can be placed in any place inside the >>> switch operator, e.g. like this: >>> >>> switch(i) { >>> case 1: System.out.println("one");break; >>> default: System.out.println("other");break; >>> case 2: System.out.println("two");break; >>> } >>> >>> In this case behavior does not change on the order of case blocks. >>> However in pattern matching the order of cases usually matters: if >>> some pattern matches, this means that the subsequent patterns will not >>> be checked. Does this mean that with pattern matching the default >>> branch makes all the subsequent case blocks unreachable? Or default >>> can still be located anywhere and is checked only after any other >>> pattern? >>> >>> With best regards, >>> Tagir Valeev >>> >>> >>> >>> >>> -- >>> Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb at google.com > -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Thu Dec 14 20:08:05 2017 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 14 Dec 2017 15:08:05 -0500 Subject: default branch placement in switch In-Reply-To: <2518601F-A2C0-4B7E-BF66-5584A453FF9B@oracle.com> References: <4740da81-8d99-645f-fa22-f587b6433278@oracle.com> <2518601F-A2C0-4B7E-BF66-5584A453FF9B@oracle.com> Message-ID: That would also be OK, as the two different versions of the rule are not very far apart in practice -- nearly all switches that will take a broader argument type will also want to do something more interesting with it (type test, deconstruct, etc.) On 12/14/2017 2:42 PM, Guy Steele wrote: > I think this is the right approach. > > An alternative that should at least be considered would be to further > simplify the statement of the exception by eliminating the mention of > legacy types: > > ?- For switches which have only constant case labels (and "default"), > we relax the above?rule regarding default. > > >> On Dec 14, 2017, at 2:39 PM, Brian Goetz > > wrote: >> >> Here's where I think we should come down on this. >> >> First, we should appeal to the general rule about pattern ordering; >> if pattern X dominates Y (that is, everything that matches Y also >> matches X), X can't come before Y. >> >> This gives us: >> ?- "case null" must precede any non-constant case label >> ?- "default" must come at the end. >> >> Now, we carve out special dispensation for existing switches: >> ?- For switches whose static argument type is one of the legacy >> switch types (primitives, boxes, string, enum) *and* which have only >> constant case labels (and "default"), we relax the above rule >> regarding default. >> >> >> >> On 12/11/2017 2:15 PM, Brian Goetz wrote: >>> The middle is surely awful.? Though in the JDK, we have a fair >>> number of uses where default is the _first_ case, which isn't >>> unreasonable, and some might argue is even clearer in some cases. >>> >>> The reason to tread lightly on forcing reorganization of existing >>> switches is that it is allowable to fall into *and out of* the >>> default case.? So if someone has: >>> >>> ??? switch (x) { >>> ??????? default:? S; ? // fall through >>> ??????? case COMMON: T; break; >>> ??????? case UNCOMMON: U; break; >>> ??? } >>> >>> then eventually getting to an error when default is not last for >>> "legacy" switches (those where all labels are type-restating >>> constants) means some uncomfortable refactoring just to "make the >>> compiler happy."? So while I agree on warnings, I'm not sure if we >>> can ever get to error in all cases without picking some fights with >>> users. >>> >>> >>> >>> On 12/11/2017 1:16 PM, Kevin Bourrillion wrote: >>>> On Fri, Nov 3, 2017 at 5:25 PM, Brian Goetz >>> > wrote: >>>> >>>> >>>> or plan to eventually get to a place where default always comes >>>> last, even for "int" switches. If we want to get to the latter, >>>> we should start warning on this construct now. >>>> >>>> >>>> I favor starting to warn and eventually forbidding default in any >>>> position but last for all constructs that have it. >>>> >>>> A switch with the default in the middle is extremely weird and >>>> confusing. If I'm reading code to understand what happens when i == >>>> 3, and I read as far as >>>> >>>> switch (i) { >>>> ? case 1: >>>> ? ? justOneStuff(); break(); >>>> ? case 2: >>>> ? ? justTwoStuff(); break(); >>>> ? default: >>>> >>>> ... then I immediately assume that this must be where execution is >>>> continuing. Worse, even if I do notice that there are more case >>>> labels to follow, and I resume searching for a `case 3:`, then when >>>> I don't find one I now risk making /another/?error and forgetting >>>> to jump /back/ to the default. >>>> >>>> This is kind of insane. At first I was less worried because I >>>> thought "surely no one is actually doing this"... then I browsed >>>> our codebase.... yikes. >>>> >>>> We should at least strongly consider this. >>>> >>>> >>>> >>>> On 11/3/2017 5:10 PM, Tagir Valeev wrote: >>>> >>>> Hello! >>>> >>>> Currently the default branch can be placed in any place >>>> inside the >>>> switch operator, e.g. like this: >>>> >>>> switch(i) { >>>> case 1: System.out.println("one");break; >>>> default: System.out.println("other");break; >>>> case 2: System.out.println("two");break; >>>> } >>>> >>>> In this case behavior does not change on the order of case >>>> blocks. >>>> However in pattern matching the order of cases usually >>>> matters: if >>>> some pattern matches, this means that the subsequent >>>> patterns will not >>>> be checked. Does this mean that with pattern matching the >>>> default >>>> branch makes all the subsequent case blocks unreachable? Or >>>> default >>>> can still be located anywhere and is checked only after any >>>> other >>>> pattern? >>>> >>>> With best regards, >>>> Tagir Valeev >>>> >>>> >>>> >>>> >>>> >>>> -- >>>> Kevin Bourrillion?|?Java Librarian |?Google, >>>> Inc.?|kevinb at google.com >>> >> > -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Thu Dec 14 20:44:52 2017 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 14 Dec 2017 15:44:52 -0500 Subject: Switching on float/double/long In-Reply-To: <917C6484-5AEF-43E2-9D0A-2CA5CBB4671F@oracle.com> References: <1801453083.1035590.1513115572265.JavaMail.zimbra@u-pem.fr> <917C6484-5AEF-43E2-9D0A-2CA5CBB4671F@oracle.com> Message-ID: <63bcb271-10b2-9b64-8f5f-3331fd123f5b@oracle.com> > I see no corresponding reason to delay longs. ?Instead, I see a pressing > need to figure out the correct relation between switch (x) { case > (byte)1; } > where x might be a long or Long. ?I don't see a way to delay that > decision. As outlined in Gavin's note about many things (patterns, generics, null, and primitives), I think there is a pretty good answer in there, which is to (at least initially) forbid non-type-restating numeric constant and primitive type test patterns.? This gives us the freedom to stop there, or expand in several directions regarding primitives (box-centric vs value-set) without committing to either right now. The fundamental problem with numeric literals is that while there's only one way to convert 0.0d to Object (box to Double and widen), there are many ways to convert 0 to Object (box to any of { Byte, Short, Integer, Long, Character } and then widen).? The language helpfully provides automatic conversions so we are not irritated by being constantly asked "which zero did you intend to assign to this int?"? But this makes for some real trouble when we try to define the semantics of things like "x matches 0", when the static type of x is Object.? Should we match all of: (Byte) 0, (Short) 0, (Character) 0, (Integer) 0, (Long) 0, and maybe even float and double?? This adds a lot of complexity for very little gain.? Instead, we tell the user: "that's an imprecise question, please ask it more precisely." We already have defined semantics of what switch does when the switch argument is a primitive or box, and the case label is a numeric constant.? So that question is answered.? The new question is, what if the switch argument is something broader, like Number or Object?? And the simple answer is: disallow numeric constant patterns here (with the possible exception of manifestly typed constants, like 0.0f, which only matches Float zero), because they're too imprecise. Two observations: ?- This won't happen very often; its rare that you know so little about the type of a target, but are particularly concerned that it might be the number seven. ?- When it does happen, there are easy ways to delegate to existing equality comparisons that are more explicit (such as type test patterns with guards.) For example: ??? Object o = ... ??? switch (o) { ??????? case Integer n ??????????? where n == 7: ... ??????? case Number n ??????????? where n.intValue() == 7: ... ??? } So, we choose to take no heroic measures to try to decipher the relationship between numeric constants and broadly-typed targets like Number or Object.? Give me more type information, and you get more flexibility. We do the same with primitive type test patterns; you don't get to say "case int" in the above switch, lest you think that it might be testing to see if the target is a boxed number that falls into the int value-set range.? Instead, you say "case Integer".? If the target type is narrower (say, Integer), then you can say "case int" because that's effectively type-restating. Returning to floating point, this means that we need only define semantics of matching a floating-point context to something already known to be a float. On 12/13/2017 7:51 PM, John Rose wrote: > Joe's points make perfect sense to me. > > Because of distinct problems with float, double, and reference operand > types, the "==" operator of Java is a poor equivalence relation, so just > referring the semantics of switch to op== is IMO a false start for > defining > switch. ?Switch-on-string has already broken with that false start, in the > case of references, using Object.equals. ?A coherent way to break from > op== on floats is to, also, refer to the closest possible Object.equals > method, that on Float (and Double). ?Joe's proposal in fact appeals to > the same standards, that of floatToIntBits. > > https://docs.oracle.com/javase/7/docs/api/java/lang/Float.html#equals(java.lang.Object) > > > The most fine-grained equality relation that can be defined across all > types does not have an API point, but it can be called "substitutability". > For references substitutability is simply acmp, or op==(Object,Object). > For floats, substitutability is approximated by equality on > floatToIntBits, > but defined rigorously by equality on floatToRawIntBits, which preserves > distinctions among NaNs. ?Since those distinctions can be observed by > code, two distinct NaNs cannot be said to be substitutable for each > other. > > Joe's comparison, and that of Float.equals, is slightly more > coarse-grained > of an equivalence relation, because all the NaNs are grouped into a single > cluster. ?I wish the designer of Float.equals had not stopped > arbitrarily at > NaN folding, and used floatToRawIntBits. ?But, given that history, I think > when switch supports floats and doubles, it will use Joe's comparison. > > As Remi points out, suitable third-party extractors (or value type > wrappers) > can provide other relations besides Joe's default, either distinguishing > NaNs or lumping zeroes. ?Perhaps even rejecting NaNs, since they aren't > equal to themselves, supposedly. > > But we only get to set the default once. ?So perhaps we should delay > supporting floats directly, until we can put all three or four float > matching predicates in front of us and decide which is the default. > > I see no corresponding reason to delay longs. ?Instead, I see a pressing > need to figure out the correct relation between switch (x) { case > (byte)1; } > where x might be a long or Long. ?I don't see a way to delay that > decision. > > Backing up a bit, I prefer to evaluate match semantics in terms of > assignment > detection, rather than ad hoc equality predicates. ?If the story is > only ad > hoc, "if this type then this predicate" I am sure it will have more nasty > corners than it needs. ?If it has an overarching principle, then I am sure > it will have nasty corners (as with +0 and NaNs), but only a minimum > of them. ?And the overarching principle I prefer for match is to ask the > following polymorphic question: ?"Could a value just like this case > expression have been assigned to that switch variable?" ?This, IMO, > unwinds a lot of otherwise ad hoc special pleading. ?It does require > some ad hoc definition of what "just like this" means, but the rest falls > out of prior JLS semantics. ?Including the vexed questions which will > be occurring to you, above, about Long vs. long vs. byte. > > ? John > > On Dec 12, 2017, at 1:52 PM, Remi Forax > wrote: >> >> While we could do that, use bits representation for float and double, >> this is typically the kind of things that a user can also do with a >> record (a value type record ?) and a deconstructor, so in my opinion, >> we should not rush to implement this kind of switch given that we >> will soon provide a general mechanism to implement them outside of >> the JDK. >> >> R?mi >> >> ------------------------------------------------------------------------ >> >> *De:*"Brian Goetz" > > >> *?:*"amber-spec-experts" > > >> *Envoy?:*Lundi 11 D?cembre 2017 22:25:34 >> *Objet:*Switching on float/double/long >> >> A target of opportunity for the new switch JEP is to fill out the >> set of types that traditional switches can operate on -- >> specifically float, double, and long.? The reason that we don't >> support these now is mostly an accident of history; the >> `tableswitch` and `lookupswitch` opcodes are int-based, so the >> compiler doesn't have a convenient target for translating these.? >> As you've seen from the recent notes on switch translation, we're >> working towards using indy more broadly as a translation target >> for most switch constructs.? This makes it far easier to bust the >> limitations on switch argument types, and so this has been listed >> as a target of opportunity in the JEP (for both statement and >> expression switches.) >> >> Our resident floating-point expert, Joe Darcy, offers the >> following additional thoughts on the subject: >> >> -- Begin forwarded message >> >> Per a recent request from Brian, I've written a few thoughts >> about switching on floating-point values. >> >> To address some common misunderstandings of floating-point, while >> it is often recommended to*not*compare floating-point values for >> equality, it is perfectly well-defined to do such comparisons, it >> just might not do what you want >> >> For example, instead of >> >> ??? // Infinite loop since sum stored in d never exactly equals >> 1.0, doh! >> ??? while(d != 1.0)\u000B >> ??????? d += 0.1; >> >> use either >> >> ??? // Counted loop >> ??? for(int i = 0; i < 10; i++)\u000B >> ??????? d += 0.1; >> >> or >> >> ??? // Stop when numerical threshold is met >> ??? while(d <= 1.0)\u000B >> ??????? d += 0.1; >> >> depending on the semantics the loop is trying to capture. >> >> I've attached a slide from my JVMLS talk this year to help >> illustrate the semantic modeling going in in IEEE 754 >> floating-point. Each of the 232possible bit patterns of a float >> is some floating-point value, likewise for the 264possible bit >> patterns of a double. However, from a Java language or JVM >> perspective, there are not 232or 264distinct values we need or >> want to distinguish in most cases. In particular, we almost >> always want to treat all bit patterns which encode a NaN as a >> single conceptual NaN. Another wrinkle concerns zero: IEEE 754 >> has both a positive zero and a negative zero. Why are >> there*two*zeros? Because there are two infinities.? The signed >> infinities and distinguished by divide (1.0/0.0 => +infinity, >> 1.0/-0.0 => -infinity) and by various library functions. >> >> So we want to: >> >> ??? * Allow every distinct finite nonzero floating-point value to >> be? the case of a switch. >> ??? * Allow -0.0 and +0.0 to be treated separately. >> ??? * Allow -infinity and +infinity to be treated separately. >> ??? * Collapse all NaN representation as a single value. >> >> For the "Rounding" mapping in the diagram which goes from the >> extended real numbers to floating-point data, there is a nonempty >> segment of the real number line which maps to a given >> representable floating-point number. For example, besides the >> string "1.0" mapping exactly to the reprentable floating-point >> value 1.0, there is a region slightly small than 1 >> (0.99999999999999999999...) which will round up to 1.0 and a >> region slightly larger than 1 (1.000000000000000001...) which >> will round down to 1 from decimal -> binary conversion. This >> would need to be factored into any distinctiveness requirements >> for the different arms of the switch. In other words >> >> ??? case 1.000000000000000001: >> ??? .... >> ??? case 0.99999999999999999999 >> ??? ... >> >> would need to be rejected just as >> >> ??? case 0: >> ??? .... >> ??? case 00: >> >> is rejected. >> >> In terms of JDK 9 structures and operations, the following >> transformation of a float switch has what I think are reasonable >> semantics: >> >> ??? Replace each float case label y in the source with an int >> label resulting from floatToIntBits(y). Note that floatToIntBits >> is used for the mapping rather than floatToRawIntBits since we >> want NaNs to be grouped together. >> >> ??? Instead of switching on float value x, switch on >> floatToIntBits(x). >> >> HTH, >> >> -Joe >> > -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Thu Dec 14 21:22:48 2017 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 14 Dec 2017 16:22:48 -0500 Subject: Switch expressions -- some revisions Message-ID: <3d1f794f-4694-32ee-a56c-d82d1d9e05f1@oracle.com> After reviewing the feedback on the proposal for switch expressions, and a bit of going back to the drawing board, I have some proposed changes to the plan outlined in the JEP. 1.? Throw expressions.? While throw expressions are a reasonable feature, many expressed concern that if permitted too broadly (such as in method invocation context), they would encourage "tricky" code for little incremental expressiveness.? The real need here is for arms of expression switches to be able to throw when an unexpected state is encountered; secondarily it may be useful allow a value-bearing lambda to unconditionally throw as well.? But extending this to &&, ||, assignment, and method invocation context seems like asking for trouble.? So we'll narrow the treatment here, allowing throw on the RHS of a switch expression ARM, and possibly also the RHS of a lambda.? (This doesn't close any doors on making `throw` an expression later, if desired.) 2.? Local return from switch.? In the proposal, we borrowed the convention from lambda to use "return" for nonlocal return, mostly on the theory of "follow the arrow".? But this is pretty uncomfortable, made worse by several factors: a) despite the syntactic similarity, we don't follow exactly the same rules for case arms of expression switches as for lambdas (such as treatment of captured vars), and b) when refactoring from statement switch to expression switch or vice versa, there's a danger that an existing "return" could silently swap between nonlocal and local return semantics. So we dusted off an old idea, which we'd previously explored but which had some challenges, which is to use "break" with an operand instead of "return" to indicate local return in switch expressions.? So: ??? int y = switch(abs(x)) { ??????? case 1 -> 1; ??????? case 2 -> 2; ??????? case 3 -> 3; ??????? default -> { ??????????? println("bigger than 3"); ??????????? break x; ??????? } ??? }; The challenge is ambiguity; this could be interpreted as a nonlocal break out of an enclosing loop whose label is `x`.? But then we realized that if `x` is both a variable and a label, we can just reject this, and tell the user to rename one or the other; since alpha-renaming the label is always source- and binary-compatible, the user has at least one (if not two) reasonable choices to get out of this problem. The benefit here is that now "break" means basically the same thing in an expression switch as it does in a statement switch; it terminates evaluation of the switch, providing a value if one is needed.? Having addressed the ambiguity problem, I think this is a slam-dunk, as it aligns expression switch and statement switch quite a bit (same capture rules, same control flow statements.) We can also, if we like, support "break" for local return in lambdas (we should have done this in 8), to align the two. 3.? (Optional.)? There's room to take (2) farther if we want, which is to complete the transformation by eliminating the fake "block expression" in favor of something more like existing switch.? The idea would be to borrow from statement switches, and rewrite the above example as (note where we use colon vs arrow): ??? int y = switch(abs(x)) { ??????? case 1 -> 1; ??????? case 2 -> 2; ??????? case 3 -> 3; ??????? default: ??????????? println("more than 3"); ??????????? break x; ??? }; So in this context, then "case L -> e" in an expression switch is just sugar for "case L: break e".? As with lambdas, I expect the statements+break form to be pretty rare, but we still need to have a way to do it (not all objects can be created in a single expression without resorting to stupid tricks.) A good way to think about this is that this is leaving statement switch completely alone, and then expression switch "extends" statement switch, adding the nice arrow shorthand and the exhaustiveness analysis.? The downside is that expression switch is even more "infected" by existing switch semantics, but after thinking about it for a while, this doesn't bother me.? (It's more uniform, plus its considerably harder to make the "accidental fallthrough" mistake in an expression switch than a statement switch.) I expect this proposal will be a little more controversial than (2) -- mostly because some are probably holding out hope that we'd radically rework existing switch -- but it has the major advantage of further building on existing switch, and also refrains from introducing a similar but different kind of fake block expression. Overall this is is more of a "build on what's there" solution, rather than "add something new in the gap." -------------- next part -------------- An HTML attachment was scrubbed... URL: From kevinb at google.com Thu Dec 14 21:41:49 2017 From: kevinb at google.com (Kevin Bourrillion) Date: Thu, 14 Dec 2017 13:41:49 -0800 Subject: Switch expressions -- some revisions In-Reply-To: <3d1f794f-4694-32ee-a56c-d82d1d9e05f1@oracle.com> References: <3d1f794f-4694-32ee-a56c-d82d1d9e05f1@oracle.com> Message-ID: On Thu, Dec 14, 2017 at 1:22 PM, Brian Goetz wrote: 2. Local return from switch. In the proposal, we borrowed the convention > from lambda to use "return" for nonlocal return, mostly on the theory of > "follow the arrow". But this is pretty uncomfortable, made worse by > several factors: a) despite the syntactic similarity, we don't follow > exactly the same rules for case arms of expression switches as for lambdas > (such as treatment of captured vars), and b) when refactoring from > statement switch to expression switch or vice versa, there's a danger that > an existing "return" could silently swap between nonlocal and local return > semantics. > > So we dusted off an old idea, which we'd previously explored but which had > some challenges, which is to use "break" with an operand instead of > "return" to indicate local return in switch expressions. So: > > int y = switch(abs(x)) { > case 1 -> 1; > case 2 -> 2; > case 3 -> 3; > default -> { > println("bigger than 3"); > break x; > } > }; > The choice of `break` here feels very uncomfortable to me. The whole nature of expression switch is that you never have to mess around with your control flow the way you do in statement switch. The idea that you're "breaking" out of anything doesn't apply in this world, so it just feels opportunistic to repurpose the keyword for something else. I understood `return` and I'm trying to figure out if the problems you describe with it are really that harmful. And if they are that harmful... would we consider just removing the keyword and allowing this block to end with a naked expression? i.e., you have zero or more statements, followed by one expression. I don't know if this is good or bad. The challenge is ambiguity; this could be interpreted as a nonlocal break > out of an enclosing loop whose label is `x`. But then we realized that if > `x` is both a variable and a label, we can just reject this, and tell the > user to rename one or the other; since alpha-renaming the label is always > source- and binary-compatible, the user has at least one (if not two) > reasonable choices to get out of this problem. > This technically dispenses with the conflict, but that the conflict even exists at all between two such unrelated constructs (an expression and a label) is very weird. The benefit here is that now "break" means basically the same thing in an > expression switch as it does in a statement switch; it terminates > evaluation of the switch, providing a value if one is needed. > I'm not (currently) seeing it this way at all. It seems a superficial similarity. In one form of switch you are responsible for control flow and in the other you don't need to worry about it. It fits, to me, that you should only see `break` in the former. > We can also, if we like, support "break" for local return in lambdas (we > should have done this in 8), to align the two. > (Seems even weirder to me?) > 3. (Optional.) There's room to take (2) farther if we want, which is to > complete the transformation by eliminating the fake "block expression" in > favor of something more like existing switch. The idea would be to borrow > from statement switches, and rewrite the above example as (note where we > use colon vs arrow): > > int y = switch(abs(x)) { > case 1 -> 1; > case 2 -> 2; > case 3 -> 3; > default: > println("more than 3"); > break x; > }; > Not liking the mixed motif here. Just my 1 1/2 cents, that's all. > > So in this context, then "case L -> e" in an expression switch is just > sugar for "case L: break e". As with lambdas, I expect the > statements+break form to be pretty rare, but we still need to have a way to > do it (not all objects can be created in a single expression without > resorting to stupid tricks.) > > A good way to think about this is that this is leaving statement switch > completely alone, and then expression switch "extends" statement switch, > adding the nice arrow shorthand and the exhaustiveness analysis. The > downside is that expression switch is even more "infected" by existing > switch semantics, but after thinking about it for a while, this doesn't > bother me. (It's more uniform, plus its considerably harder to make the > "accidental fallthrough" mistake in an expression switch than a statement > switch.) > > I expect this proposal will be a little more controversial than (2) -- > mostly because some are probably holding out hope that we'd radically > rework existing switch -- but it has the major advantage of further > building on existing switch, and also refrains from introducing a similar > but different kind of fake block expression. Overall this is is more of a > "build on what's there" solution, rather than "add something new in the > gap." > > > -- Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb at google.com -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Thu Dec 14 22:12:30 2017 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 14 Dec 2017 17:12:30 -0500 Subject: Switch expressions -- some revisions In-Reply-To: References: <3d1f794f-4694-32ee-a56c-d82d1d9e05f1@oracle.com> Message-ID: > So we dusted off an old idea, which we'd previously explored but > which had some challenges, which is to use "break" with an operand > instead of "return" to indicate local return in switch > expressions.? So: > > ??? int y = switch(abs(x)) { > ??????? case 1 -> 1; > ??????? case 2 -> 2; > ??????? case 3 -> 3; > ??????? default -> { > ??????????? println("bigger than 3"); > ??????????? break x; > ??????? } > ??? }; > > > The choice of `break` here feels very uncomfortable to me. The whole > nature of expression switch is that you never have to mess around with > your control flow the way you do in statement switch. The idea that > you're "breaking" out of anything doesn't apply in this world, so it > just feels opportunistic to repurpose the keyword for something else. No, it's not a repurposing -- it's the same semantics, extended to the value-bearing nature of an expression switch. Let's make an analogy with "return".? Return has two forms: ? - "return" -- for void methods ? - "return e" -- for non-void methods In both cases, it means "stop executing the enclosing method right now, and here's a final value to give to whoever got me started, if they're expecting a value." So for "break" in switch, we have: ?- "break" -- for statement (void) switches ?- "break e" -- for expression (non-void) switches In both cases, it means "stop executing the enclosing switch now, and here's a final value to give to whoever got me started, if they're expecting a value." Using "return" here to mean "yield value locally from expression" is the repurposing.? With lambdas we could kind of get away with it because we knew at some level lambdas were going to be translated down to methods, but it was always uncomfortable.? With statement switches, it's even more of a stretch.? Sure, you can squint and say "the case arm is like a lambda whose args are the binding variables and whose body is the RHS", but it does require squinting. > > I understood `return` and I'm trying to figure out if the problems you > describe with it are really that harmful. > > And if they are that harmful... would we consider just removing the > keyword and allowing this block to end with a naked expression?? i.e., > you have zero or more statements, followed by one expression. I don't > know if this is good or bad. Definitely not.? Yes, I know it works in other languages (where everything is an expression), but I think this is a non-starter for Java. > > ??? int y = switch(abs(x)) { > ??????? case 1 -> 1; > ??????? case 2 -> 2; > ??????? case 3 -> 3; > ??????? default: > ??????????? println("more than 3"); > ??????????? break x; > ??? }; > > Not liking the mixed motif here. The mixed delimiters are the warty bit of it, true.? But what this does is define expression-switch directly on top of existing switch, yielding a result which feels smaller and far less "nailed on the side" than the previous proposal. To summarize (2) + (3); pretend you never saw the original proposal with blocks.? It's like saying: ?- We're going to take existing switch, and extend it to work both as a statement and an expression; ?- DA analysis gets extended to ensure that expression switches are exhaustive; ?- In an expression context, break takes an operand to indicate the value to be yielded up as the value of the expression; ?- Plus the nice -> e syntactic shorthand for the overwhelmingly common case, so you almost never have to type "break". And that's it -- nothing else new.? It's simpler, and builds directly on what we already have. -------------- next part -------------- An HTML attachment was scrubbed... URL: From john.r.rose at oracle.com Thu Dec 14 22:17:55 2017 From: john.r.rose at oracle.com (John Rose) Date: Thu, 14 Dec 2017 14:17:55 -0800 Subject: Switch expressions -- some revisions In-Reply-To: References: <3d1f794f-4694-32ee-a56c-d82d1d9e05f1@oracle.com> Message-ID: <9DF4CD34-43D5-446C-B98F-40FD7C34D420@oracle.com> On Dec 14, 2017, at 2:12 PM, Brian Goetz wrote: > > With statement switches, it's even more of a stretch. Sure, you can squint and say "the case arm is like a lambda whose args are the binding variables and whose body is the RHS", but it does require squinting. +1 In short: Using break to mean "exit the current switch with a value" is the only conservative keyword-based solution, because break already means "exit the switch". C'mon, Kevin, you see that, right? The naked expression move has already been weighed and discarded, during the Lambda exercise. I don't see any new information that would cause us to reopen that as an option. (Although it would have been my preference at the time.) ? John -------------- next part -------------- An HTML attachment was scrubbed... URL: From kevinb at google.com Thu Dec 14 22:34:10 2017 From: kevinb at google.com (Kevin Bourrillion) Date: Thu, 14 Dec 2017 14:34:10 -0800 Subject: Switch expressions -- some revisions In-Reply-To: <9DF4CD34-43D5-446C-B98F-40FD7C34D420@oracle.com> References: <3d1f794f-4694-32ee-a56c-d82d1d9e05f1@oracle.com> <9DF4CD34-43D5-446C-B98F-40FD7C34D420@oracle.com> Message-ID: On Thu, Dec 14, 2017 at 2:17 PM, John Rose wrote: > On Dec 14, 2017, at 2:12 PM, Brian Goetz wrote: > > > With statement switches, it's even more of a stretch. Sure, you can > squint and say "the case arm is like a lambda whose args are the binding > variables and whose body is the RHS", but it does require squinting. > > > +1 In short: > > Using break to mean "exit the current switch with a value" is > the only conservative keyword-based solution, because break > already means "exit the switch". > > C'mon, Kevin, you see that, right? > > The naked expression move has already been weighed and > discarded, during the Lambda exercise. I don't see any new > information that would cause us to reopen that as an option. > (Although it would have been my preference at the time.) > Fair enough... knowing that option is out, and starting to see that `return` is more problematic than I first realized, is getting me closer to being able to tolerate "break this value". The "return/return e/break/break e" analogy Brian just gave helps as well. -- Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb at google.com -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Thu Dec 14 22:46:50 2017 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 14 Dec 2017 17:46:50 -0500 Subject: Switch expressions -- some revisions In-Reply-To: References: <3d1f794f-4694-32ee-a56c-d82d1d9e05f1@oracle.com> <9DF4CD34-43D5-446C-B98F-40FD7C34D420@oracle.com> Message-ID: <14100909-51f7-29fe-b9dc-b493c11457b6@oracle.com> > The "return/return e/break/break e" analogy Brian just gave helps as well. My take-away from Kevin's reaction is: some will immediately see how "break n" is just a generalization of existing break, and some people will not immediately see it -- and when you don't, it feels uncomfortable. Being in the first category, I had a hard time imagining being in the second category, but here we have a good existence proof that it can be reasonably inhabited, so we need to consider the pedagogical tradeoffshere too. (What I like about where this proposal leads is that it is building expression switch atop existing switch, and only adding the minimum new stuff needed to support expression-ness (yielding a value, enhanced flow analysis), plus a little sugar.) Ironically, the fact that the "case L -> e" form will be so prevalent makes it harder to get comfortable with the traditional syntax, because it will occur so rarely.? (This is an example where we get so hooked on our sugar we forget it is sugar.) > starting to see that `return` is more problematic than I first realized I didn't see this one at first either; it only hit me later when I thought about refactoring between expression and statement switch.? And, like those figure/ground optical illusions, once you see it, you can't unsee it... From forax at univ-mlv.fr Thu Dec 14 22:59:53 2017 From: forax at univ-mlv.fr (Remi Forax) Date: Thu, 14 Dec 2017 23:59:53 +0100 (CET) Subject: Switch expressions -- some revisions In-Reply-To: <3d1f794f-4694-32ee-a56c-d82d1d9e05f1@oracle.com> References: <3d1f794f-4694-32ee-a56c-d82d1d9e05f1@oracle.com> Message-ID: <232479318.1987368.1513292393693.JavaMail.zimbra@u-pem.fr> > De: "Brian Goetz" > ?: "amber-spec-experts" > Envoy?: Jeudi 14 D?cembre 2017 22:22:48 > Objet: Switch expressions -- some revisions > After reviewing the feedback on the proposal for switch expressions, and a bit > of going back to the drawing board, I have some proposed changes to the plan > outlined in the JEP. > 1. Throw expressions. While throw expressions are a reasonable feature, many > expressed concern that if permitted too broadly (such as in method invocation > context), they would encourage "tricky" code for little incremental > expressiveness. The real need here is for arms of expression switches to be > able to throw when an unexpected state is encountered; secondarily it may be > useful allow a value-bearing lambda to unconditionally throw as well. But > extending this to &&, ||, assignment, and method invocation context seems like > asking for trouble. So we'll narrow the treatment here, allowing throw on the > RHS of a switch expression ARM, and possibly also the RHS of a lambda. (This > doesn't close any doors on making `throw` an expression later, if desired.) > 2. Local return from switch. In the proposal, we borrowed the convention from > lambda to use "return" for nonlocal return, mostly on the theory of "follow the > arrow". But this is pretty uncomfortable, made worse by several factors: a) > despite the syntactic similarity, we don't follow exactly the same rules for > case arms of expression switches as for lambdas (such as treatment of captured > vars), and b) when refactoring from statement switch to expression switch or > vice versa, there's a danger that an existing "return" could silently swap > between nonlocal and local return semantics. > So we dusted off an old idea, which we'd previously explored but which had some > challenges, which is to use "break" with an operand instead of "return" to > indicate local return in switch expressions. So: > int y = switch(abs(x)) { > case 1 -> 1; > case 2 -> 2; > case 3 -> 3; > default -> { > println("bigger than 3"); > break x; > } > }; > The challenge is ambiguity; this could be interpreted as a nonlocal break out of > an enclosing loop whose label is `x`. But then we realized that if `x` is both > a variable and a label, we can just reject this, and tell the user to rename > one or the other; since alpha-renaming the label is always source- and > binary-compatible, the user has at least one (if not two) reasonable choices to > get out of this problem. > The benefit here is that now "break" means basically the same thing in an > expression switch as it does in a statement switch; it terminates evaluation of > the switch, providing a value if one is needed. Having addressed the ambiguity > problem, I think this is a slam-dunk, as it aligns expression switch and > statement switch quite a bit (same capture rules, same control flow > statements.) We can also, if we like, support "break" for local return in > lambdas (we should have done this in 8), to align the two. I'm Ok with break value, having 'return' with two different meaning is dangerous, but i still think we should no have the same keyword for the switch and the expression switch (so we will not name i the expression switch), whatever keyword is fine for me but not switch, it's not the same semantics. In a language that as no statement or allow the value of a block to be the value of the last expression, it makes sense to have the same construct for statement and expression, but Java makes a difference between expression and statement, its' a legacy from the C. I've proposed a more lightweight syntax for switch expression because it's what the C or Java do, you have a lightweight syntax if it's an expression and a more heavyweight syntax if it's a statement. Now, again, we can keep the 'case', introduce ->, use break with a value for the expression switch but i strongly feel we should not use the same keyword, i.e. for people of the future, Java will have two ways to specify the matching one using switch which is a statement and one using 'put your own keyword here' which is an expression. R?mi -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Fri Dec 15 00:24:39 2017 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 14 Dec 2017 19:24:39 -0500 Subject: Switch expressions -- some revisions In-Reply-To: <232479318.1987368.1513292393693.JavaMail.zimbra@u-pem.fr> References: <3d1f794f-4694-32ee-a56c-d82d1d9e05f1@oracle.com> <232479318.1987368.1513292393693.JavaMail.zimbra@u-pem.fr> Message-ID: I think there are good arguments on both sides of this issue. Because, expression switch and statement switch, they have both commonality and difference. Overall, my gut sense is that "lumping" is better than "splitting" here.? Especially as there are so many possible dimensions of lump vs split: expression vs statement, constants vs patterns, exhaustive vs not, fallthrough vs not, colons vs arrows, etc. Your point about "people of the future" is a good one.? When a "Java 9 person" encounters expression switch for the first time, the lumping may help (or might make it harder) to understand the commonalities vs differences.? On the other hand, a "Java 15 person" looking back in history may have a different perspective, and we should design for the future person as being a newbie is (hopefully) a short-lived experience. So, for where we want to land, does it make more sense to accentuate the commonality ("do exactly one of these things, based on these choices") or the difference ("operate via side-effects vs computation")?? My sense is the former, but others may have different opinions. On 12/14/2017 5:59 PM, Remi Forax wrote: > but i still think we should no have the same keyword for the switch > and the expression switch (so we will not name i the expression > switch), whatever keyword is fine for me but not switch, it's not the > same semantics. From daniel.smith at oracle.com Fri Dec 15 19:58:03 2017 From: daniel.smith at oracle.com (Dan Smith) Date: Fri, 15 Dec 2017 12:58:03 -0700 Subject: Switching on float/double/long In-Reply-To: <917C6484-5AEF-43E2-9D0A-2CA5CBB4671F@oracle.com> References: <1801453083.1035590.1513115572265.JavaMail.zimbra@u-pem.fr> <917C6484-5AEF-43E2-9D0A-2CA5CBB4671F@oracle.com> Message-ID: > On Dec 13, 2017, at 5:51 PM, John Rose wrote: > > But we only get to set the default once. So perhaps we should delay > supporting floats directly, until we can put all three or four float > matching predicates in front of us and decide which is the default. > On Dec 14, 2017, at 9:09 AM, Kevin Bourrillion wrote: > > Switch on long: sure. > > Switch on float/double: why? When you guys say you'd like to _not_ support floating-points, I'm not sure what you mean. The input to a switch can have any type, including Object. Some Objects may be Floats or Doubles. A constant pattern may be any constant expression. Some constant expressions are floats or doubles. I guess we could claim that floating-point constants can't be constant patterns, but that would be an awkward exclusion, since any other constant expressions work. We've got an ugly assortment of different flavors of "constant", and it would sure be nice not to add another. Anyway, details aside, what you think you're asking for (I presume) is to hold off on implementing a new feature, but the reality seems to be that the request means actively prohibiting something that otherwise would Just Work. ?Dan From daniel.smith at oracle.com Fri Dec 15 20:01:45 2017 From: daniel.smith at oracle.com (Dan Smith) Date: Fri, 15 Dec 2017 13:01:45 -0700 Subject: default branch placement in switch In-Reply-To: <2518601F-A2C0-4B7E-BF66-5584A453FF9B@oracle.com> References: <4740da81-8d99-645f-fa22-f587b6433278@oracle.com> <2518601F-A2C0-4B7E-BF66-5584A453FF9B@oracle.com> Message-ID: <4C21C638-2C1A-45F0-AFE5-5E78B8D58803@oracle.com> > On Dec 14, 2017, at 12:42 PM, Guy Steele wrote: > > I think this is the right approach. > > An alternative that should at least be considered would be to further simplify the statement of the exception by eliminating the mention of legacy types: > > - For switches which have only constant case labels (and "default"), we relax the above rule regarding default. +1 This rule looks like we could have come up with it when designing the language from scratch, while the more constraining rule screams "historical reasons". ?Dan From kevinb at google.com Fri Dec 15 20:44:10 2017 From: kevinb at google.com (Kevin Bourrillion) Date: Fri, 15 Dec 2017 12:44:10 -0800 Subject: Switching on float/double/long In-Reply-To: References: <1801453083.1035590.1513115572265.JavaMail.zimbra@u-pem.fr> <917C6484-5AEF-43E2-9D0A-2CA5CBB4671F@oracle.com> Message-ID: On Fri, Dec 15, 2017 at 11:58 AM, Dan Smith wrote: > > On Dec 13, 2017, at 5:51 PM, John Rose wrote: > > > > But we only get to set the default once. So perhaps we should delay > > supporting floats directly, until we can put all three or four float > > matching predicates in front of us and decide which is the default. > > > On Dec 14, 2017, at 9:09 AM, Kevin Bourrillion > wrote: > > > > Switch on long: sure. > > > > Switch on float/double: why? > > When you guys say you'd like to _not_ support floating-points, I'm not > sure what you mean. > > The input to a switch can have any type, including Object. If this much is given, then yes, of course we should allow `switch (primitiveDouble)`. I was trying to connect the dots before and I think I've got them now. My *current* limited understanding is that (a) it makes a lot of sense for the pattern-matching feature to use the existing switch construct, and (b) it follows inescapably from that that `switch (somePrimitiveDouble)` will work no matter what (via boxing) so therefore, right, we might as well just support it. Is that a reasonable way to put it? -- Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb at google.com -------------- next part -------------- An HTML attachment was scrubbed... URL: From guy.steele at oracle.com Fri Dec 15 20:38:36 2017 From: guy.steele at oracle.com (Guy Steele) Date: Fri, 15 Dec 2017 15:38:36 -0500 Subject: Switching on float/double/long In-Reply-To: <917C6484-5AEF-43E2-9D0A-2CA5CBB4671F@oracle.com> References: <1801453083.1035590.1513115572265.JavaMail.zimbra@u-pem.fr> <917C6484-5AEF-43E2-9D0A-2CA5CBB4671F@oracle.com> Message-ID: > On Dec 13, 2017, at 7:51 PM, John Rose wrote: > . . . the overarching principle I prefer for match is to ask the > following polymorphic question: "Could a value just like this case > expression have been assigned to that switch variable?" This, IMO, > unwinds a lot of otherwise ad hoc special pleading. It does require > some ad hoc definition of what "just like this" means, but the rest falls > out of prior JLS semantics. Including the vexed questions which will > be occurring to you, above, about Long vs. long vs. byte. > > ? John +1 ?Guy From brian.goetz at oracle.com Fri Dec 15 20:55:48 2017 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 15 Dec 2017 15:55:48 -0500 Subject: Switching on float/double/long In-Reply-To: References: <1801453083.1035590.1513115572265.JavaMail.zimbra@u-pem.fr> <917C6484-5AEF-43E2-9D0A-2CA5CBB4671F@oracle.com> Message-ID: Yes, that's right. However, we can still place restrictions on _switch_ if we felt they were warranted.? For example, we could prohibit switch from taking arguments whose static type is float/Float, if we wanted to, or restrict constant `case` labels from having floating point patterns, without restricting nested matches (e.g., Complex(var re, 0.0d)).? Not saying that's a good idea, but we could do so without pulling the rug out from under the pattern matching tower.? (We play a related game with nulls.) One restriction I think we should place (I've written about this before) is that we should restrict type-polymorphic numeric constant patterns and primitive type-test patterns in all pattern-matching contexts, except where they are type-restating. So these are OK: ??? switch (anInt) { ??????? case 0: ... ??????? case int x ??????????? where x < 0: ... ??? } ??? switch (anInteger) { ??????? case 0: ... ??????? case int x ??????????? where x < 0: ... ??? } ??? switch (aFloat) { ??????? case 0.0f: ... ? ? ? ? case float f ? ? ? ? ? ? where f < 0.0f: ... ??? } But I think we should restrict this: ??? switch (anObject) { ??????? case 0: ... ??????? case int x: ... ??? } This is because of the ambiguity that "Object 0" could be Integer 0, Long 0, Short 0, etc.? If you mean `Integer`, say `Integer` here. On 12/15/2017 3:44 PM, Kevin Bourrillion wrote: > On Fri, Dec 15, 2017 at 11:58 AM, Dan Smith > wrote: > > > On Dec 13, 2017, at 5:51 PM, John Rose > wrote: > > > > But we only get to set the default once.? So perhaps we should delay > > supporting floats directly, until we can put all three or four float > > matching predicates in front of us and decide which is the default. > > > On Dec 14, 2017, at 9:09 AM, Kevin Bourrillion > > wrote: > > > > Switch on long: sure. > > > > Switch on float/double: why? > > When you guys say you'd like to _not_ support floating-points, I'm > not sure what you mean. > > The input to a switch can have any type, including Object. > > > If this much is given, then yes, of course we should allow `switch > (primitiveDouble)`. I was trying to connect the dots before and I > think I've got them now. My /current/ limited understanding is that > (a) it makes a lot of sense for the pattern-matching feature to use > the existing switch construct, and (b) it follows inescapably from > that that `switch (somePrimitiveDouble)` will work no matter what (via > boxing) so therefore, right, we might as well just support it. Is that > a reasonable way to put it? > > > -- > Kevin Bourrillion?|?Java Librarian |?Google, Inc.?|kevinb at google.com > -------------- next part -------------- An HTML attachment was scrubbed... URL: From kevinb at google.com Mon Dec 18 21:16:07 2017 From: kevinb at google.com (Kevin Bourrillion) Date: Mon, 18 Dec 2017 13:16:07 -0800 Subject: New JEP: Switch Expressions for the Java Language In-Reply-To: References: <1932fb57-fda7-ac1d-57ec-2d062b56766e@oracle.com> <6F1D8934-488E-4643-A006-4D1CC57C70E7@oracle.com> <746568769.2856118.1512932376514.JavaMail.zimbra@u-pem.fr> <27A87FD0-E010-449A-B488-BE120BDCEC3D@oracle.com> Message-ID: On Mon, Dec 11, 2017 at 11:28 AM, Brian Goetz wrote: > > I think most of my questions surround how these changes will (or *should*) > affect non-expression switches. > > - Should we retcon `case A, B:` onto regular switches, with the same > semantics? > > Absolutely; I thought this was already present in the JEP, but if not, > I'll clarify. > > > - Should we retcon `case null:` onto regular switches, with the same > semantics? > > Absolutely; same comment. > There is a problem here, especially among users who learn Java in the future and never know how `switch` used to work. They will know that `null` is a valid thing to switch on, and when they don't see `case null` they will assume that the `default` clause is being executed. That's unfortunate. Even for those of us who have been around, we have always seen case ANYTHING: default: codeHere(); as a weird thing to do. Now whenever we want null handled the default way we're going to have to do this: case null: default: codeHere(); This isn't an absolute deal-breaker, but I could use a refresher on what the positive value of allowing `case null` is at all. The word "null" does not appear in the current pattern matching doc. (I will be digging up stats on the prevalence of "faking" a null case with `if`, but I suspect it's not very much.) -------------- next part -------------- An HTML attachment was scrubbed... URL: From guy.steele at oracle.com Mon Dec 18 21:08:23 2017 From: guy.steele at oracle.com (Guy Steele) Date: Mon, 18 Dec 2017 16:08:23 -0500 Subject: Switch expressions -- some revisions In-Reply-To: <3d1f794f-4694-32ee-a56c-d82d1d9e05f1@oracle.com> References: <3d1f794f-4694-32ee-a56c-d82d1d9e05f1@oracle.com> Message-ID: <8007EFCA-48D8-4965-B0D9-AA9615F6B625@oracle.com> > On Dec 14, 2017, at 4:22 PM, Brian Goetz wrote: > > . . . > > So we dusted off an old idea, which we'd previously explored but which had some challenges, which is to use "break" with an operand instead of "return" to indicate local return in switch expressions. So: > > int y = switch(abs(x)) { > case 1 -> 1; > case 2 -> 2; > case 3 -> 3; > default -> { > println("bigger than 3"); > break x; > } > }; > > The challenge is ambiguity; this could be interpreted as a nonlocal break out of an enclosing loop whose label is `x`. But then we realized that if `x` is both a variable and a label, we can just reject this, and tell the user to rename one or the other; since alpha-renaming the label is always source- and binary-compatible, the user has at least one (if not two) reasonable choices to get out of this problem. . . . By all means, reject the ambiguous situation; this sounds fine. But also advise the user of another possibility: if the intent is to return the value of the variable `x` (rather than use the label), just use parentheses and write `break (x);`. Which I might take to doing in all cases anyway, just to make it _immediately_ clear even in nonambiguous cases that `x` is not a label. ?Guy From brian.goetz at oracle.com Mon Dec 18 21:25:15 2017 From: brian.goetz at oracle.com (Brian Goetz) Date: Mon, 18 Dec 2017 16:25:15 -0500 Subject: New JEP: Switch Expressions for the Java Language In-Reply-To: References: <1932fb57-fda7-ac1d-57ec-2d062b56766e@oracle.com> <6F1D8934-488E-4643-A006-4D1CC57C70E7@oracle.com> <746568769.2856118.1512932376514.JavaMail.zimbra@u-pem.fr> <27A87FD0-E010-449A-B488-BE120BDCEC3D@oracle.com> Message-ID: <9a6abb45-bf29-e933-e7de-c7d7c24f5f8c@oracle.com> On 12/18/2017 4:16 PM, Kevin Bourrillion wrote: > On Mon, Dec 11, 2017 at 11:28 AM, Brian Goetz > wrote: > >> I think most of my questions surround how these changes will (or >> /should/) affect non-expression switches. >> >> * Should we retcon `case A, B:` onto regular switches, with the >> same semantics? >> > Absolutely; I thought this was already present in the JEP, but if > not, I'll clarify. > >> * Should we retcon `case null:` onto regular switches, with the >> same semantics? >> > Absolutely; same comment. > > > There is a problem here, especially among users who learn Java in the > future and never know how `switch` used to work. > They will know that `null` is a valid thing to switch on, and when > they don't see `case null` they will assume that the `default` clause > is being executed. That's unfortunate. > Why would they assume that?? What they'll learn in future Java school is "If there's no explicit case null, switch throws NPE". Which is exactly as it is today -- except that you can't spell "case null".? In no case will a switch without a "case null" execute the default clause on null input. > Now whenever we want null handled the default way we're going to have > to do this: > > case null: > default: > ? codeHere(); Right.? This seems pretty clean to me -- if you want null lumped into default, you say so, and its clear.? Otherwise you get the default null treatment. > This isn't an absolute deal-breaker, but I could use a refresher on > what the positive value of allowing `case null` is at all. The word > "null" does not appear in the current pattern matching doc. Same answer as with floats: nested patterns and composibility. Suppose I have a class Box, which is a one-element collection, and let's say a Box can contain null.? (Nulls are reasonable, if tricky values for domain classes.)? I want to be able to say: ??? case Box(null): ... which is a nested pattern that means: ??? case Box(\alpha) where \alpha matches null: which brings us back to: everything is cleaner if null is a just constant pattern that can be matched.? At which point, it just seems a little mean to have null be a pattern but *not* be able to say: ??? case null: in a switch at top level.? Especially because you should be allowed to refactor: ??? case Box(null): A ??? case Box(String s): B; ??? case Box(Frogs fs): C; into ??? case Box(var x): ??????? switch (x) { ??????????? case null: A ? ? ? ?? ?? case String s: B; ? ? ? ?? ?? case Frogs fs: C; ??????? } if that seems beneficial. -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Mon Dec 18 21:41:12 2017 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Mon, 18 Dec 2017 22:41:12 +0100 (CET) Subject: New JEP: Switch Expressions for the Java Language In-Reply-To: <9a6abb45-bf29-e933-e7de-c7d7c24f5f8c@oracle.com> References: <1932fb57-fda7-ac1d-57ec-2d062b56766e@oracle.com> <746568769.2856118.1512932376514.JavaMail.zimbra@u-pem.fr> <27A87FD0-E010-449A-B488-BE120BDCEC3D@oracle.com> <9a6abb45-bf29-e933-e7de-c7d7c24f5f8c@oracle.com> Message-ID: <192423762.461348.1513633272613.JavaMail.zimbra@u-pem.fr> > De: "Brian Goetz" > ?: "Kevin Bourrillion" > Cc: "Remi Forax" , "amber-spec-experts" > > Envoy?: Lundi 18 D?cembre 2017 22:25:15 > Objet: Re: New JEP: Switch Expressions for the Java Language > On 12/18/2017 4:16 PM, Kevin Bourrillion wrote: >> On Mon, Dec 11, 2017 at 11:28 AM, Brian Goetz < [ mailto:brian.goetz at oracle.com >> | brian.goetz at oracle.com ] > wrote: >>>> I think most of my questions surround how these changes will (or should ) affect >>>> non-expression switches. >>>> * Should we retcon `case A, B:` onto regular switches, with the same semantics? >>> Absolutely; I thought this was already present in the JEP, but if not, I'll >>> clarify. >>>> * Should we retcon `case null:` onto regular switches, with the same semantics? >>> Absolutely; same comment. >> There is a problem here, especially among users who learn Java in the future and >> never know how `switch` used to work. >> They will know that `null` is a valid thing to switch on, and when they don't >> see `case null` they will assume that the `default` clause is being executed. >> That's unfortunate. > Why would they assume that? What they'll learn in future Java school is "If > there's no explicit case null, switch throws NPE". Which is exactly as it is > today -- except that you can't spell "case null". In no case will a switch > without a "case null" execute the default clause on null input. yes, this is how i will teach it :) R?mi -------------- next part -------------- An HTML attachment was scrubbed... URL: From kevinb at google.com Mon Dec 18 21:53:24 2017 From: kevinb at google.com (Kevin Bourrillion) Date: Mon, 18 Dec 2017 13:53:24 -0800 Subject: New JEP: Switch Expressions for the Java Language In-Reply-To: <192423762.461348.1513633272613.JavaMail.zimbra@u-pem.fr> References: <1932fb57-fda7-ac1d-57ec-2d062b56766e@oracle.com> <746568769.2856118.1512932376514.JavaMail.zimbra@u-pem.fr> <27A87FD0-E010-449A-B488-BE120BDCEC3D@oracle.com> <9a6abb45-bf29-e933-e7de-c7d7c24f5f8c@oracle.com> <192423762.461348.1513633272613.JavaMail.zimbra@u-pem.fr> Message-ID: Yes, that is how everyone *should* be taught. Of course, many (many) people end up reading and modifying code without a whole lot of "teaching" ever having happened. And of course we want to carefully consider the plight of that developer as we make changes -- I don't think that is controversial. Having become aware that `case null` is a thing, from reading it in class A, it will be pretty reasonable for that developer to assume it's being handled by the `default` code in class B. I'll repeat that this isn't a deal-breaker - I just think it matters. -- Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb at google.com -------------- next part -------------- An HTML attachment was scrubbed... URL: From kevinb at google.com Mon Dec 18 21:55:02 2017 From: kevinb at google.com (Kevin Bourrillion) Date: Mon, 18 Dec 2017 13:55:02 -0800 Subject: New JEP: Switch Expressions for the Java Language In-Reply-To: <9a6abb45-bf29-e933-e7de-c7d7c24f5f8c@oracle.com> References: <1932fb57-fda7-ac1d-57ec-2d062b56766e@oracle.com> <6F1D8934-488E-4643-A006-4D1CC57C70E7@oracle.com> <746568769.2856118.1512932376514.JavaMail.zimbra@u-pem.fr> <27A87FD0-E010-449A-B488-BE120BDCEC3D@oracle.com> <9a6abb45-bf29-e933-e7de-c7d7c24f5f8c@oracle.com> Message-ID: On Mon, Dec 18, 2017 at 1:25 PM, Brian Goetz wrote: > > > On 12/18/2017 4:16 PM, Kevin Bourrillion wrote: > > On Mon, Dec 11, 2017 at 11:28 AM, Brian Goetz > wrote: >> >> I think most of my questions surround how these changes will (or *should*) >> affect non-expression switches. >> >> - Should we retcon `case A, B:` onto regular switches, with the same >> semantics? >> >> Absolutely; I thought this was already present in the JEP, but if not, >> I'll clarify. >> >> >> - Should we retcon `case null:` onto regular switches, with the same >> semantics? >> >> Absolutely; same comment. >> > > There is a problem here, especially among users who learn Java in the > future and never know how `switch` used to work. > > They will know that `null` is a valid thing to switch on, and when they > don't see `case null` they will assume that the `default` clause is being > executed. That's unfortunate. > > > Why would they assume that? What they'll learn in future Java school is > "If there's no explicit case null, switch throws NPE". Which is exactly as > it is today -- except that you can't spell "case null". In no case will a > switch without a "case null" execute the default clause on null input. > > Now whenever we want null handled the default way we're going to have to > do this: > > case null: > default: > codeHere(); > > > Right. This seems pretty clean to me -- if you want null lumped into > default, you say so, and its clear. Otherwise you get the default null > treatment. > > This isn't an absolute deal-breaker, but I could use a refresher on what > the positive value of allowing `case null` is at all. The word "null" does > not appear in the current pattern matching doc. > > > Same answer as with floats: nested patterns and composibility. > > Suppose I have a class Box, which is a one-element collection, and > let's say a Box can contain null. (Nulls are reasonable, if tricky values > for domain classes.) I want to be able to say: > > case Box(null): ... > > which is a nested pattern that means: > > case Box(\alpha) where \alpha matches null: > > which brings us back to: everything is cleaner if null is a just constant > pattern that can be matched. > I'm with you so far. > At which point, it just seems a little mean to have null be a pattern but > *not* be able to say: > > case null: > > in a switch at top level. Especially because you should be allowed to > refactor: > > case Box(null): A > case Box(String s): B; > case Box(Frogs fs): C; > > into > > case Box(var x): > switch (x) { > case null: A > case String s: B; > case Frogs fs: C; > } > > if that seems beneficial. > To me, this amounts to enough justification *if there were no drawbacks*, but given the potential for some confusion exists, I am still trying to convince myself why this matters enough to outweigh that. -- Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb at google.com -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Tue Dec 19 19:40:50 2017 From: brian.goetz at oracle.com (Brian Goetz) Date: Tue, 19 Dec 2017 14:40:50 -0500 Subject: Switch expressions -- some revisions In-Reply-To: <3d1f794f-4694-32ee-a56c-d82d1d9e05f1@oracle.com> References: <3d1f794f-4694-32ee-a56c-d82d1d9e05f1@oracle.com> Message-ID: I've updated the JEP to reflect these proposed changes: ??? https://bugs.openjdk.java.net/browse/JDK-8192963 On 12/14/2017 4:22 PM, Brian Goetz wrote: > > After reviewing the feedback on the proposal for switch expressions, > and a bit of going back to the drawing board, I have some proposed > changes to the plan outlined in the JEP. > > > 1.? Throw expressions.? While throw expressions are a reasonable > feature, many expressed concern that if permitted too broadly (such as > in method invocation context), they would encourage "tricky" code for > little incremental expressiveness.? The real need here is for arms of > expression switches to be able to throw when an unexpected state is > encountered; secondarily it may be useful allow a value-bearing lambda > to unconditionally throw as well.? But extending this to &&, ||, > assignment, and method invocation context seems like asking for > trouble.? So we'll narrow the treatment here, allowing throw on the > RHS of a switch expression ARM, and possibly also the RHS of a lambda. > (This doesn't close any doors on making `throw` an expression later, > if desired.) > > > 2.? Local return from switch.? In the proposal, we borrowed the > convention from lambda to use "return" for nonlocal return, mostly on > the theory of "follow the arrow".? But this is pretty uncomfortable, > made worse by several factors: a) despite the syntactic similarity, we > don't follow exactly the same rules for case arms of expression > switches as for lambdas (such as treatment of captured vars), and b) > when refactoring from statement switch to expression switch or vice > versa, there's a danger that an existing "return" could silently swap > between nonlocal and local return semantics. > > So we dusted off an old idea, which we'd previously explored but which > had some challenges, which is to use "break" with an operand instead > of "return" to indicate local return in switch expressions.? So: > > ??? int y = switch(abs(x)) { > ??????? case 1 -> 1; > ??????? case 2 -> 2; > ??????? case 3 -> 3; > ??????? default -> { > ??????????? println("bigger than 3"); > ??????????? break x; > ??????? } > ??? }; > > The challenge is ambiguity; this could be interpreted as a nonlocal > break out of an enclosing loop whose label is `x`.? But then we > realized that if `x` is both a variable and a label, we can just > reject this, and tell the user to rename one or the other; since > alpha-renaming the label is always source- and binary-compatible, the > user has at least one (if not two) reasonable choices to get out of > this problem. > > The benefit here is that now "break" means basically the same thing in > an expression switch as it does in a statement switch; it terminates > evaluation of the switch, providing a value if one is needed.? Having > addressed the ambiguity problem, I think this is a slam-dunk, as it > aligns expression switch and statement switch quite a bit (same > capture rules, same control flow statements.) We can also, if we like, > support "break" for local return in lambdas (we should have done this > in 8), to align the two. > > > 3.? (Optional.)? There's room to take (2) farther if we want, which is > to complete the transformation by eliminating the fake "block > expression" in favor of something more like existing switch.? The idea > would be to borrow from statement switches, and rewrite the above > example as (note where we use colon vs arrow): > > ??? int y = switch(abs(x)) { > ??????? case 1 -> 1; > ??????? case 2 -> 2; > ??????? case 3 -> 3; > ??????? default: > ??????????? println("more than 3"); > ??????????? break x; > ??? }; > > So in this context, then "case L -> e" in an expression switch is just > sugar for "case L: break e".? As with lambdas, I expect the > statements+break form to be pretty rare, but we still need to have a > way to do it (not all objects can be created in a single expression > without resorting to stupid tricks.) > > A good way to think about this is that this is leaving statement > switch completely alone, and then expression switch "extends" > statement switch, adding the nice arrow shorthand and the > exhaustiveness analysis.? The downside is that expression switch is > even more "infected" by existing switch semantics, but after thinking > about it for a while, this doesn't bother me.? (It's more uniform, > plus its considerably harder to make the "accidental fallthrough" > mistake in an expression switch than a statement switch.) > > I expect this proposal will be a little more controversial than (2) -- > mostly because some are probably holding out hope that we'd radically > rework existing switch -- but it has the major advantage of further > building on existing switch, and also refrains from introducing a > similar but different kind of fake block expression.? Overall this is > is more of a "build on what's there" solution, rather than "add > something new in the gap." > > -------------- next part -------------- An HTML attachment was scrubbed... URL: From guy.steele at oracle.com Tue Dec 19 20:03:03 2017 From: guy.steele at oracle.com (Guy Steele) Date: Tue, 19 Dec 2017 15:03:03 -0500 Subject: Switch expressions -- some revisions In-Reply-To: References: <3d1f794f-4694-32ee-a56c-d82d1d9e05f1@oracle.com> Message-ID: <246C3EE2-9C15-4DE3-8336-C5494EB4D05B@oracle.com> Good. This sentence: The sugared form of case arms of switch expressions may also throw exceptions, even though throw e is a statement, not an expressions. (?an expressions???) could perhaps be replaced or augmented by this syntactically more precise observation: In a switch expression, we also define case LABEL -> throw expression; to be sugar for case LABEL: throw expression; Also, there is a formatting problem: the text line System.out.println("Neither Foo nor Bar, hmmm..."); break 3; } should have been part of the code in the preceding box. > On Dec 19, 2017, at 2:40 PM, Brian Goetz wrote: > > I've updated the JEP to reflect these proposed changes: > > https://bugs.openjdk.java.net/browse/JDK-8192963 > > On 12/14/2017 4:22 PM, Brian Goetz wrote: >> >> After reviewing the feedback on the proposal for switch expressions, and a bit of going back to the drawing board, I have some proposed changes to the plan outlined in the JEP. >> >> >> 1. Throw expressions. While throw expressions are a reasonable feature, many expressed concern that if permitted too broadly (such as in method invocation context), they would encourage "tricky" code for little incremental expressiveness. The real need here is for arms of expression switches to be able to throw when an unexpected state is encountered; secondarily it may be useful allow a value-bearing lambda to unconditionally throw as well. But extending this to &&, ||, assignment, and method invocation context seems like asking for trouble. So we'll narrow the treatment here, allowing throw on the RHS of a switch expression ARM, and possibly also the RHS of a lambda. (This doesn't close any doors on making `throw` an expression later, if desired.) >> >> >> 2. Local return from switch. In the proposal, we borrowed the convention from lambda to use "return" for nonlocal return, mostly on the theory of "follow the arrow". But this is pretty uncomfortable, made worse by several factors: a) despite the syntactic similarity, we don't follow exactly the same rules for case arms of expression switches as for lambdas (such as treatment of captured vars), and b) when refactoring from statement switch to expression switch or vice versa, there's a danger that an existing "return" could silently swap between nonlocal and local return semantics. >> >> So we dusted off an old idea, which we'd previously explored but which had some challenges, which is to use "break" with an operand instead of "return" to indicate local return in switch expressions. So: >> >> int y = switch(abs(x)) { >> case 1 -> 1; >> case 2 -> 2; >> case 3 -> 3; >> default -> { >> println("bigger than 3"); >> break x; >> } >> }; >> >> The challenge is ambiguity; this could be interpreted as a nonlocal break out of an enclosing loop whose label is `x`. But then we realized that if `x` is both a variable and a label, we can just reject this, and tell the user to rename one or the other; since alpha-renaming the label is always source- and binary-compatible, the user has at least one (if not two) reasonable choices to get out of this problem. >> >> The benefit here is that now "break" means basically the same thing in an expression switch as it does in a statement switch; it terminates evaluation of the switch, providing a value if one is needed. Having addressed the ambiguity problem, I think this is a slam-dunk, as it aligns expression switch and statement switch quite a bit (same capture rules, same control flow statements.) We can also, if we like, support "break" for local return in lambdas (we should have done this in 8), to align the two. >> >> >> 3. (Optional.) There's room to take (2) farther if we want, which is to complete the transformation by eliminating the fake "block expression" in favor of something more like existing switch. The idea would be to borrow from statement switches, and rewrite the above example as (note where we use colon vs arrow): >> >> int y = switch(abs(x)) { >> case 1 -> 1; >> case 2 -> 2; >> case 3 -> 3; >> default: >> println("more than 3"); >> break x; >> }; >> >> So in this context, then "case L -> e" in an expression switch is just sugar for "case L: break e". As with lambdas, I expect the statements+break form to be pretty rare, but we still need to have a way to do it (not all objects can be created in a single expression without resorting to stupid tricks.) >> >> A good way to think about this is that this is leaving statement switch completely alone, and then expression switch "extends" statement switch, adding the nice arrow shorthand and the exhaustiveness analysis. The downside is that expression switch is even more "infected" by existing switch semantics, but after thinking about it for a while, this doesn't bother me. (It's more uniform, plus its considerably harder to make the "accidental fallthrough" mistake in an expression switch than a statement switch.) >> >> I expect this proposal will be a little more controversial than (2) -- mostly because some are probably holding out hope that we'd radically rework existing switch -- but it has the major advantage of further building on existing switch, and also refrains from introducing a similar but different kind of fake block expression. Overall this is is more of a "build on what's there" solution, rather than "add something new in the gap." >> >> > -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Tue Dec 19 20:21:41 2017 From: brian.goetz at oracle.com (Brian Goetz) Date: Tue, 19 Dec 2017 15:21:41 -0500 Subject: Switch expressions -- some revisions In-Reply-To: <246C3EE2-9C15-4DE3-8336-C5494EB4D05B@oracle.com> References: <3d1f794f-4694-32ee-a56c-d82d1d9e05f1@oracle.com> <246C3EE2-9C15-4DE3-8336-C5494EB4D05B@oracle.com> Message-ID: Updated. On 12/19/2017 3:03 PM, Guy Steele wrote: > Good. > > This sentence: > > ??The sugared form of case arms of?switch?expressions may also throw > exceptions, even though?throw e?is a statement, not an expressions. > > (?an expressions???) could perhaps be replaced or augmented by this > syntactically more precise observation: > > ? In a switch expression, we also define > > case LABEL -> throw expression; > > ? to be sugar for > > case LABEL: throw expression; > > Also, there is a formatting problem: the text line > > System.out.println("Neither Foo nor Bar, hmmm..."); break 3; } > > should have been part of the code in the preceding box. > > > >> On Dec 19, 2017, at 2:40 PM, Brian Goetz > > wrote: >> >> I've updated the JEP to reflect these proposed changes: >> >> https://bugs.openjdk.java.net/browse/JDK-8192963 >> >> On 12/14/2017 4:22 PM, Brian Goetz wrote: >>> >>> After reviewing the feedback on the proposal for switch expressions, >>> and a bit of going back to the drawing board, I have some proposed >>> changes to the plan outlined in the JEP. >>> >>> >>> 1.? Throw expressions.? While throw expressions are a reasonable >>> feature, many expressed concern that if permitted too broadly (such >>> as in method invocation context), they would encourage "tricky" code >>> for little incremental expressiveness.? The real need here is for >>> arms of expression switches to be able to throw when an unexpected >>> state is encountered; secondarily it may be useful allow a >>> value-bearing lambda to unconditionally throw as well.? But >>> extending this to &&, ||, assignment, and method invocation context >>> seems like asking for trouble.? So we'll narrow the treatment here, >>> allowing throw on the RHS of a switch expression ARM, and possibly >>> also the RHS of a lambda.? (This doesn't close any doors on making >>> `throw` an expression later, if desired.) >>> >>> >>> 2.? Local return from switch.? In the proposal, we borrowed the >>> convention from lambda to use "return" for nonlocal return, mostly >>> on the theory of "follow the arrow".? But this is pretty >>> uncomfortable, made worse by several factors: a) despite the >>> syntactic similarity, we don't follow exactly the same rules for >>> case arms of expression switches as for lambdas (such as treatment >>> of captured vars), and b) when refactoring from statement switch to >>> expression switch or vice versa, there's a danger that an existing >>> "return" could silently swap between nonlocal and local return >>> semantics. >>> >>> So we dusted off an old idea, which we'd previously explored but >>> which had some challenges, which is to use "break" with an operand >>> instead of "return" to indicate local return in switch expressions.? >>> So: >>> >>> ??? int y = switch(abs(x)) { >>> ??????? case 1 -> 1; >>> ??????? case 2 -> 2; >>> ??????? case 3 -> 3; >>> ??????? default -> { >>> ??????????? println("bigger than 3"); >>> ??????????? break x; >>> ??????? } >>> ??? }; >>> >>> The challenge is ambiguity; this could be interpreted as a nonlocal >>> break out of an enclosing loop whose label is `x`.? But then we >>> realized that if `x` is both a variable and a label, we can just >>> reject this, and tell the user to rename one or the other; since >>> alpha-renaming the label is always source- and binary-compatible, >>> the user has at least one (if not two) reasonable choices to get out >>> of this problem. >>> >>> The benefit here is that now "break" means basically the same thing >>> in an expression switch as it does in a statement switch; it >>> terminates evaluation of the switch, providing a value if one is >>> needed.? Having addressed the ambiguity problem, I think this is a >>> slam-dunk, as it aligns expression switch and statement switch quite >>> a bit (same capture rules, same control flow statements.) We can >>> also, if we like, support "break" for local return in lambdas (we >>> should have done this in 8), to align the two. >>> >>> >>> 3.? (Optional.)? There's room to take (2) farther if we want, which >>> is to complete the transformation by eliminating the fake "block >>> expression" in favor of something more like existing switch.? The >>> idea would be to borrow from statement switches, and rewrite the >>> above example as (note where we use colon vs arrow): >>> >>> ??? int y = switch(abs(x)) { >>> ??????? case 1 -> 1; >>> ??????? case 2 -> 2; >>> ??????? case 3 -> 3; >>> ??????? default: >>> ??????????? println("more than 3"); >>> ??????????? break x; >>> ??? }; >>> >>> So in this context, then "case L -> e" in an expression switch is >>> just sugar for "case L: break e".? As with lambdas, I expect the >>> statements+break form to be pretty rare, but we still need to have a >>> way to do it (not all objects can be created in a single expression >>> without resorting to stupid tricks.) >>> >>> A good way to think about this is that this is leaving statement >>> switch completely alone, and then expression switch "extends" >>> statement switch, adding the nice arrow shorthand and the >>> exhaustiveness analysis.? The downside is that expression switch is >>> even more "infected" by existing switch semantics, but after >>> thinking about it for a while, this doesn't bother me.? (It's more >>> uniform, plus its considerably harder to make the "accidental >>> fallthrough" mistake in an expression switch than a statement switch.) >>> >>> I expect this proposal will be a little more controversial than (2) >>> -- mostly because some are probably holding out hope that we'd >>> radically rework existing switch -- but it has the major advantage >>> of further building on existing switch, and also refrains from >>> introducing a similar but different kind of fake block expression.? >>> Overall this is is more of a "build on what's there" solution, >>> rather than "add something new in the gap." >>> >>> >> > -------------- next part -------------- An HTML attachment was scrubbed... URL: