From maurizio.cimadamore at oracle.com Fri Jul 7 12:56:52 2017 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Fri, 7 Jul 2017 13:56:52 +0100 Subject: [patterns] on treatment of null Message-ID: Hi, over the last few weeks we've been exploring the twisted relationship between patterns and nulls. This document: http://cr.openjdk.java.net/~mcimadamore/nulls-patterns.html provides some (hopefully helpful) insights into what the design space looks like. tl;dr; Looks like trying to force the same rule on all patterns, regardless of where they appear, leads to problems. Distinguishing between toplevel and nested patterns provides a good basis to handle null in a more predictable/flexible fashion. From guy.steele at oracle.com Fri Jul 7 14:13:35 2017 From: guy.steele at oracle.com (Guy Steele) Date: Fri, 7 Jul 2017 10:13:35 -0400 Subject: [patterns] on treatment of null In-Reply-To: References: Message-ID: <76CFB2E5-E4BE-4A5D-B4E7-F8749B564E72@oracle.com> > On Jul 7, 2017, at 8:56 AM, Maurizio Cimadamore wrote: > > Hi, > over the last few weeks we've been exploring the twisted relationship between patterns and nulls. This document: > > http://cr.openjdk.java.net/~mcimadamore/nulls-patterns.html > > provides some (hopefully helpful) insights into what the design space looks like. > > tl;dr; > > Looks like trying to force the same rule on all patterns, regardless of where they appear, leads to problems. Distinguishing between toplevel and nested patterns provides a good basis to handle null in a more predictable/flexible fashion. > Nice write-up! But there is one other direction that it does not seem to explore: extending the type system to have explicitly non-null reference types (an old idea, but perhaps the correct solution for patterns). String can be String or null String! cannot be null if (o matches String! x) { ? o cannot be null here ? } if (o matches LinkedList(Object! head, LinkedList tail)) { ? head cannot be null here ? } Not sure it?s where we want to go, but at least it should be explicitly considered, if only to explicitly reject it. ?Guy From maurizio.cimadamore at oracle.com Fri Jul 7 15:05:25 2017 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Fri, 7 Jul 2017 16:05:25 +0100 Subject: [patterns] on treatment of null In-Reply-To: <76CFB2E5-E4BE-4A5D-B4E7-F8749B564E72@oracle.com> References: <76CFB2E5-E4BE-4A5D-B4E7-F8749B564E72@oracle.com> Message-ID: On 07/07/17 15:13, Guy Steele wrote: >> On Jul 7, 2017, at 8:56 AM, Maurizio Cimadamore wrote: >> >> Hi, >> over the last few weeks we've been exploring the twisted relationship between patterns and nulls. This document: >> >> http://cr.openjdk.java.net/~mcimadamore/nulls-patterns.html >> >> provides some (hopefully helpful) insights into what the design space looks like. >> >> tl;dr; >> >> Looks like trying to force the same rule on all patterns, regardless of where they appear, leads to problems. Distinguishing between toplevel and nested patterns provides a good basis to handle null in a more predictable/flexible fashion. >> > Nice write-up! But there is one other direction that it does not seem to explore: extending the type system to have explicitly non-null reference types (an old idea, but perhaps the correct solution for patterns). > > String can be String or null > String! cannot be null > > if (o matches String! x) { ? o cannot be null here ? } > > if (o matches LinkedList(Object! head, LinkedList tail)) { ? head cannot be null here ? } > > Not sure it?s where we want to go, but at least it should be explicitly considered, if only to explicitly reject it. You are right - there is a connection between the lack of denotability of non-nullnesss and the failure of some of the options considered in the document (esp. option 2). One of the things that leaves a bit of a sour taste is that with '!' in place, you do have a difference between: case List l: case List(...) l: The former means nullable, the latter means non-nullable (or at least, should, because you can dereference). So, to be fully consistent, should it be written as: case List!(...) l: ? But generally, yes, this is another piece of the puzzle (and one I should have called out more explicitly in the doc). Maurizio > > ?Guy > From maurizio.cimadamore at oracle.com Fri Jul 7 15:17:23 2017 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Fri, 7 Jul 2017 16:17:23 +0100 Subject: [patterns] on treatment of null In-Reply-To: <0D7811C5-0EA9-4DD5-ABB5-6D9CC0E749A5@d-d.me> References: <0D7811C5-0EA9-4DD5-ABB5-6D9CC0E749A5@d-d.me> Message-ID: <32935cae-d976-8345-47b1-861cc9cb66b3@oracle.com> On 07/07/17 16:05, Dmitry Petrashko wrote: > > Hi Maurizio, > thanks for a good writeup. > > I?ll provide some perspective coming from Scala. > > At the very beginning of your writeup you say: > > In order to further simplify the discussion, let's also ignore var > patterns; in fact, a pattern such as var x can always be thought > of a type-test pattern where the type of the test is implicitly > inferred by the compiler using some target-type information. in > other words, given a target expression s whose static type is > String, the following two snippets should behave in the same fashion: > > if (s matches String x) { ... } > > if (s matches var x) { ... } > > This is not the case in Scala. > > |scala> (null: String) match {case a => true} // variable bound. Though > variable has type String it is null. res0: Boolean = true scala> > (null: String) match {case _: String => true} // type test. Null will > fail it. scala.MatchError: null | > > Even if Java would decide to go towards the path you?ve described, It > would be nice to be able to accommodate > alternative behaviour that Scala has. > I understand. In our mind, 'var' is simply a way to tell the compiler "type missing here, please fill it out" - and we'd like to keep the type inference as orthogonal as possible from other related semantics. > > In Scala, the runtime uses |instanceof| test (Option 1 in your writeup), > while the exhaustivity checkers uses type system and assumes absence > of null (Option 2). > For generics, we issue a warning that type parameters are unchecked. > Have you considered this option? > So, you are saying that Scala does option 1 - but it tones it a bit down by emitting a warning (rather than an harsh error) when generic types are found. This seems a sensible option - the only thing I don't understand from your description - what does Scala do for nested patterns? Is it another instanceof? If that's the case, does it means that I cannot match against a List whose head is null with a normal nested type test pattern? Maurizio > > Based on our experience it works well in practice, but it?s not clear > how applicable our experience > would be in Java. > > Best regards, > Dmitry > > On 7 Jul 2017, at 14:56, Maurizio Cimadamore wrote: > > Hi, > over the last few weeks we've been exploring the twisted > relationship between patterns and nulls. This document: > > http://cr.openjdk.java.net/~mcimadamore/nulls-patterns.html > > > provides some (hopefully helpful) insights into what the design > space looks like. > > tl;dr; > > Looks like trying to force the same rule on all patterns, > regardless of where they appear, leads to problems. Distinguishing > between toplevel and nested patterns provides a good basis to > handle null in a more predictable/flexible fashion. > -------------- next part -------------- An HTML attachment was scrubbed... URL: From darkdimius at gmail.com Fri Jul 7 16:04:28 2017 From: darkdimius at gmail.com (Dmitry Petrashko) Date: Fri, 07 Jul 2017 18:04:28 +0200 Subject: [patterns] on treatment of null In-Reply-To: <32935cae-d976-8345-47b1-861cc9cb66b3@oracle.com> References: <0D7811C5-0EA9-4DD5-ABB5-6D9CC0E749A5@d-d.me> <32935cae-d976-8345-47b1-861cc9cb66b3@oracle.com> Message-ID: <356BE109-1EFD-4C93-BCED-D6BF758508B3@gmail.com> On 7 Jul 2017, at 17:17, Maurizio Cimadamore wrote: > I understand. In our mind, 'var' is simply a way to tell the compiler > "type missing here, please fill it out" - and we'd like to keep the > type inference as orthogonal as possible from other related semantics. This is also how it behaves in scala if you don?t ascribe the type in pattern. The type will be filled in by typer. Based on http://cr.openjdk.java.net/~briangoetz/amber/pattern-match.html ``` exprswitch(b) { case Box(var n) -> n; }; ``` is equivalent to this in Scala: ``` x match { case Box(n) => n; }; ``` Or am I misinterpreting? >> >> In Scala, the runtime uses |instanceof| test (Option 1 in your >> writeup), >> while the exhaustivity checkers uses type system and assumes absence >> of null (Option 2). >> For generics, we issue a warning that type parameters are unchecked. >> Have you considered this option? >> > So, you are saying that Scala does option 1 - but it tones it a bit > down by emitting a warning (rather than an harsh error) when generic > types are found. > > This seems a sensible option - the only thing I don't understand from > your description - what does Scala do for nested patterns? Is it > another instanceof? If that's the case, does it means that I cannot > match against a List whose head is null with a normal nested type test > pattern? Scala behaves the same way for top level and nested patters: Lets say that we have `case class Cons(hd: Object, tl: Cons)` ``` x match { case Cons(a, b) => 1 // will match only if `x instanceof Cons`. will match even if a is null and b is null case Cons(a, b: Cons) => 2 // will not match if b is null, but a may be null case a: Cons => 3 // will not match null, is equivalent to the first option case a => 4 // always matches } ``` now, details about type parameters and type inference: ``` case class Cons[T](hd: T, tl: Cons) x match { case Cons(a, b) => 1 // a will be inferred to be Any, our top type case Cons[Int](a, b) => 2 // warning will be emitted that Cons[Int] is unchecked. } ``` In practice, people rarely see this warning due to important observation that compiler uses: ``` case class Cons[T](hd: T, tl: Cons) extends Seq[T] val x: Seq[Int] = ??? x match { case Cons[Int](a, b) => 2 // a warning will not be emitted, as we know that T is the same as in Seq and we ?trust? x. } ``` Best, Dmitry -------------- next part -------------- An HTML attachment was scrubbed... URL: From maurizio.cimadamore at oracle.com Fri Jul 7 16:42:21 2017 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Fri, 7 Jul 2017 17:42:21 +0100 Subject: [patterns] on treatment of null In-Reply-To: <356BE109-1EFD-4C93-BCED-D6BF758508B3@gmail.com> References: <0D7811C5-0EA9-4DD5-ABB5-6D9CC0E749A5@d-d.me> <32935cae-d976-8345-47b1-861cc9cb66b3@oracle.com> <356BE109-1EFD-4C93-BCED-D6BF758508B3@gmail.com> Message-ID: <3b134ac8-ad18-88f2-27f9-da890825d87d@oracle.com> On 07/07/17 17:04, Dmitry Petrashko wrote: > > On 7 Jul 2017, at 17:17, Maurizio Cimadamore wrote: > > I understand. In our mind, 'var' is simply a way to tell the > compiler "type missing here, please fill it out" - and we'd like > to keep the type inference as orthogonal as possible from other > related semantics. > > This is also how it behaves in scala if you don?t ascribe the type in > pattern. The type will be filled in by typer. Based on > http://cr.openjdk.java.net/~briangoetz/amber/pattern-match.html > > > |exprswitch(b) { case Box(var n) -> n; }; | > > is equivalent to this in Scala: > > |x match { case Box(n) => n; }; | > > Or am I misinterpreting? > They are the same yes. What I was trying to say, is that when there's no manifest type you seem to let 'null' in (because of the lack of instanceof under the hood), so two similarly looking patterns have different behavior w.r.t. nulls, which I'm not wild about (but I have never noticed it either having used Scala :-)) > > In Scala, the runtime uses |instanceof| test (Option 1 in your > writeup), > while the exhaustivity checkers uses type system and assumes > absence of null (Option 2). > For generics, we issue a warning that type parameters are > unchecked. > Have you considered this option? > > So, you are saying that Scala does option 1 - but it tones it a > bit down by emitting a warning (rather than an harsh error) when > generic types are found. > > This seems a sensible option - the only thing I don't understand > from your description - what does Scala do for nested patterns? Is > it another instanceof? If that's the case, does it means that I > cannot match against a List whose head is null with a normal > nested type test pattern? > > Scala behaves the same way for top level and nested patters: > Yep - that's what I feared :-) I think that, basically, the differences we're seeing here is that my claim that 'case var t' is just a shorthand for 'case T t' is not as obvious as I thought it would have been. In Scala var patterns and test patterns are two different things, and treated in quite different ways (which means different null behavior). As I said in my earlier email, I have a feeling that keeping 'var'-ness out of the equation might result in better compositionality (after all, patterns are not the only place where we intend to use 'var'). Maurizio > > Lets say that we have |case class Cons(hd: Object, tl: Cons)| > > |x match { case Cons(a, b) => 1 // will match only if `x instanceof > Cons`. will match even if a is null and b is null case Cons(a, b: > Cons) => 2 // will not match if b is null, but a may be null case a: > Cons => 3 // will not match null, is equivalent to the first option > case a => 4 // always matches } | > > now, details about type parameters and type inference: > > |case class Cons[T](hd: T, tl: Cons) x match { case Cons(a, b) => 1 // > a will be inferred to be Any, our top type case Cons[Int](a, b) => 2 > // warning will be emitted that Cons[Int] is unchecked. } | > > In practice, people rarely see this warning due to important > observation that compiler uses: > > |case class Cons[T](hd: T, tl: Cons) extends Seq[T] val x: Seq[Int] = > ??? x match { case Cons[Int](a, b) => 2 // a warning will not be > emitted, as we know that T is the same as in Seq and we ?trust? x. } | > > Best, > Dmitry > -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Fri Jul 28 13:03:12 2017 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 28 Jul 2017 09:03:12 -0400 Subject: [patterns] on treatment of null In-Reply-To: References: Message-ID: <63f8019f-46c3-c750-a5ba-fe341e12a944@oracle.com> After thinking over the options, I think #4 is the sensible direction. Not having case Foo(var x) or case Foo(Integer x) match Foo(null) seems a non-starter (#1); the "type-restatement" approach seems too subtle (#2), and having if (x matches Foo f) match null (#3) seems like bug-bait. The concern over #4 is a mostly theoretical one; that we wanted the nested pattern P(Q) to be equivalent to P(var x) && x matches Q and we were concerned that special treatment of some patterns in nested contexts would be complex. But, I don't think its that bad. If we define an operator on patterns N(P) where: N(P) = null || P // where P is a type-test pattern N(P) = P // otherwise Then, we define P(Q) == P(var x) && x matches N(Q) to address the irregularity. On 7/7/2017 8:56 AM, Maurizio Cimadamore wrote: > Hi, > over the last few weeks we've been exploring the twisted relationship > between patterns and nulls. This document: > > http://cr.openjdk.java.net/~mcimadamore/nulls-patterns.html > > provides some (hopefully helpful) insights into what the design space > looks like. > > tl;dr; > > Looks like trying to force the same rule on all patterns, regardless > of where they appear, leads to problems. Distinguishing between > toplevel and nested patterns provides a good basis to handle null in a > more predictable/flexible fashion. >