From brian.goetz at oracle.com Mon Apr 10 17:01:03 2017 From: brian.goetz at oracle.com (Brian Goetz) Date: Mon, 10 Apr 2017 13:01:03 -0400 Subject: [lvti] Handling of capture variables In-Reply-To: <488ABD21-11DC-4E4E-8462-04C4F76BB50D@oracle.com> References: <488ABD21-11DC-4E4E-8462-04C4F76BB50D@oracle.com> Message-ID: <0fd43e6c-d9f2-3df1-1d5d-3c7e1b61713a@oracle.com> Zooming out for a minute, let me illustrate the landscape for treatment of non-denotable types. There are several kinds of non-denotable types that inference might come up with: - the Null type - capture types - intersection types - anonymous class types - array types with non-denotable elements - parameterized class/interface types with non-denotable type arguments - Under JEP 301, a specialized enum constant It's also worth mentioning that some types that are currently non-denotable at the source level but denotable at the classfile level, such as anonymous class types. And, there are three possible treatments for when inference encounters a non-denotable type: - infer it anyway - reject it, make the user provide a manifest type - infer a suitable denotable supertype There are pros and cons for each of these approaches. It's also not necessarily the case we have to choose the same answer for each category of non-denotable types. Initially, we leaned towards the "reject it" answer, because it was simple and because then every program using local variable type inference had an equivalent program where every variable was manifestly typed. However, as Dan's analysis for capture types shows, this turned out to be a naive answer. Not being able to say var x = Class.forName("com.foo.Foo"); (or any of the many other, sometimes surprising, situations where capture types come up, as outlined below) would go over very poorly, not only because it would be annoying to have to provide a manifest type, but because its likely that this restriction would be an unpleasant surprise every time it came up. (Even experts are often surprised to find that certain expressions have capture variables in their type.) Also, a uniform policy across kinds of ND types is probably not desirable either. There's really no point in ever inferring the null type; the only thing you could assign to it would be null, which is rarely what the user wants. So both the "always infer" or "always reject" approaches are simplistic. For the easy cases, the answers are easy: - Null type: always reject - Anon class types / enum constants: always infer For capture types, we've already got a reasonable tool for projecting a non-denotable capture type into a reasonable denotable supertype, which Dan will explicate (and which our implementation currently implements.) With this, the answer for capture types (including array and class types that have captures in their component/parameters) is probably "project to a denotable supertype." We'll provide some details on the pros and cons of the various choices with intersection types (and some examples where intersection types pop up suprisingly.) On 3/31/2017 7:39 PM, Dan Smith wrote: > As described in the JSR 286 spec document, inferring the type of a local variable to be a non-denotable type (one that can't be written in source) is something to be careful about, due to "potential for confusion, bad error messages, or added exposure to bugs". > > The most significant area here (in terms of likely frequency) is the presence of capture variables in the type. I did some analysis of the Java SE APIs to identify and illustrate problematic cases. > > == Case 1: wildcard-parameterized return type == > > Any method (or field) that returns a wildcard-parameterized type will produce a non-denotable type on invocation, because the return type must be captured (JLS 15.12.3). > > var myClass = getClass(); > var c = Class.forName("java.lang.Object"); > var sup = String.class.getSuperclass(); > var entries = new ZipFile("/etc/filename.zip").entries(); > var joiner = Collectors.joining(" - \n", "", ""); > var plusCollector = Collectors.reducing(BigInteger.ZERO, BigInteger::add); > var future = Executors.newCachedThreadPool().submit(System::gc); > void m(MethodType type) { var ret = type.returnType(); } > void m(TreeSet set) { var comparator = set.comparator(); } > void m(Annotation ann) { var annClass = ann.annotationType(); } > void m(ReferenceQueue queue) { var stringRef = queue.poll(); } > > Using wildcards in a return type is sometimes discouraged, but other times it's the right thing to do. So while I wouldn't say these methods are pervasive, there are quite a few of them (especially where the common idiom is to almost always use a wildcard, as in Class and Collector). > > There are no capture variables present for methods that return arrays, lists, etc., of wildcard-parameterized types, because capture doesn't touch those nested wildcards: > > void m(MethodType type) { var params = type.parameterArray(); } > void m(MethodType type) { var params = type.parameterList(); } > > == Case 2: instance method returning a class type parameter == > > A method (or field) whose return type is a class type parameter will produce a capture variable when invoked for a wildcard-parameterized type. > > void m(Class c) throws Exception { var runnable = c.newInstance(); } > void m(Map map) { var e = map.get("some.key"); } > void m(List> sets) { var first = sets.get(0); } > Object find(Collection coll, Object o) { for (var elt : coll) { if (elt.equals(o)) return elt; } return null; } > void m(Optional opt) { var num = opt.get(); } > void m(IntFunction f) { var reader = f.apply(14); } > void m(Future future) { var entry = future.get(10, TimeUnit.SECONDS); } > > If you substitute a wildcard-parameterized type into the return type, that also leads to capture: > > void m(List> list) { var set = list.get(0); } > > This is true for for-each, too (for now, javac fails to perform capture correctly, so you don't see this in the prototype): > > void m(List> list) { for (var set : list) set.clear(); } > > == Method category 3: instance method returning a type that mentions a class type parameter == > > A method (or field) whose return type *mentions* a class type parameter (e.g., Iterator in Iterable.iterator) will also produce a non-denotable type when invoked for a wildcard-parameterized type. Unlike Category 2, which tend to be "terminal operations", these types often arise in chains. > > var constructor = Class.forName("java.lang.Object").getConstructor(); > void m(Map map) { var keys = map.keySet(); } > void m(Map map) { var iter = map.keySet().iterator(); } > void m(TreeMap map) { var tail = map.subMap("b", "c"); } > void m(TreeSet set) { var reverseOrder = set.comparator().reversed(); } > void m(List list) { var unique = list.stream().distinct().sorted(); } > void m(List stream) { var best = stream.min(Comparator.comparing(e -> e.getStackTrace().length)); } > void m(Function f1, Function f2) { var f = f1.andThen(f2); } > void m(Predicate discard) { var keep = discard.negate(); } > > == Case 4: method with inferred type parameter in return type == > > A method (or constructor) whose return type includes an inferred type parameter may end up substituting capture variables or other non-denotable types. This typically depends on the types of the arguments, again with a wildcard-parameterized type showing up somewhere. > > void m(Enumeration tasks) { var list = Collections.list(tasks); } > void m(Set set) { var syncSet = Collections.synchronizedSet(set); } > void m(Function f) { var es = Stream.of("a", "b", "c").map(f); } > > There are also cases here that are specified to produce capture vars but do not in javac: > > void m(List ns) { var firstSet = Collections.singleton(ns.get(0)); } > > ---------------- > > With that in mind, looking at our three options for dealing with capture variables: > 1) Allow the non-denotable type > 2) Map the type to a supertype that is denotable > 3) Report an error > > (3) isn't viable. "You can't use 'var' with 'getClass'" is already pretty bad. Prohibiting all the uses above would be really bad. > > We've thought a lot about (1) and (2). The JEP includes this example: > > void test(List l1, List l2) { > var l3 = l1; // List or List? > l3 = l2; // error? > l3.add(l3.get(0)); // error? > } > > On 'l3 = l2': I wouldn't say it's an important priority that all 'var' variables have a type that is convenient for future mutation. But we do expect users do be able to easily see *why* an assignment wouldn't be allowed. Unfortunately, capture variables are such a subtle thing that they're often invisible, and programmers don't even realize that they appear as an intermediate step. So, most people would see 'var l3 = l1' and expect that the type of l3 is List. > > On 'l3.add(l3.get(0))': This is a cool trick. The use of 'var' essentially serves the same purpose as invoking a generic method in order to give a capture variable a name: > > dupFirst(List list) { list.add(list.get(0)); } > ... > dupFirst(l1); > > On the other hand, it's a subtle trick, and the average user isn't going to understand what's going on. (Or, more likely: 'l3.add(l3.get(0))' looks fine to them, but they won't understand why it stops working when that gets refactored to 'l1.add(l1.get(0))'.) > > So, in terms of user experience, it seems like (2) is the desired outcome here. > > That choice isn't without some sacrifice: it would be a nice property if lifting a subexpression out of an expression into its own 'var' declaration yields identical types. Since (2) changes the intermediate type, that doesn't hold. That said, hopefully our mapping function is reasonably unobtrusive... > > How do we define the mapping? "Use the bound" is the easy answer, although in practice it's more complicated than that: > - Which bound? (upper or lower?) > - What if the bound contains the capture var? > - What do you do with a capture variable appearing as a (invariant) type argument? > - What do you do with a capture variable appearing as a wildcard bound? > > We're working on finalizing the details. While this operation isn't trivial, it turns out it's pretty important: we already need it to solve bugs in the type system involving type inference [1] and lambda expressions [2]. It's a useful general-purpose tool. > > ?Dan > > [1] https://bugs.openjdk.java.net/browse/JDK-8016196 > [2] https://bugs.openjdk.java.net/browse/JDK-8170887 From brian.goetz at oracle.com Mon Apr 17 20:57:28 2017 From: brian.goetz at oracle.com (Brian Goetz) Date: Mon, 17 Apr 2017 16:57:28 -0400 Subject: Adopting JEP 303 (Intrinsics for LDC and INVOKEDYNAMIC) into Amber In-Reply-To: <1011153534.242641.1492462077448.JavaMail.zimbra@u-pem.fr> References: <89d512d8-73d4-ad6f-8a5d-5d5bd08adda4@oracle.com> <1011153534.242641.1492462077448.JavaMail.zimbra@u-pem.fr> Message-ID: <97f16b95-e2b8-f314-f244-63e862742017@oracle.com> [ moving to spec list ] > It's not clear in the current draft, but the compiler should fail if it can not create a const value for a whole expression send as arguments of ldc or indy. Correct. It's fine to create a Constable with non-constant arguments: ClassConstant cc = ClassConstant.of(someNonConstantFunction()) but if the compiler can't see through the invocation to fold the result, its an error to try to intrinsify: Intrinsics.ldc(cc) // error, not a constant If the inputs are constant but refer to names not locatable via the compile-time class path, the reasonable choices are warning or error. ClassConstant cc = ClassConstant.of("Lcom/foo/Blarglewooflebark;") Intrinsics.ldc(cc) // warning, com.foo.Blarglewooflebark not known at compile time I lean towards warning, because it is imaginable one might want to LDC / indy against dynamically generated classes. > and i still think that the whole Constable interface should not be visible from the user, only Constantables and Intrinsics should be visible. If the Constable type is not visible, then we can't constrain the arguments of ldc / indy to be Constable, which is a useful thing (it will keep people from making mistakes.) > also all intermediary objects should be value types, no ? Value-based, yes. We don't have value types yet :) From brian.goetz at oracle.com Mon Apr 17 21:33:35 2017 From: brian.goetz at oracle.com (Brian Goetz) Date: Mon, 17 Apr 2017 17:33:35 -0400 Subject: Adopting JEP 303 (Intrinsics for LDC and INVOKEDYNAMIC) into Amber In-Reply-To: <97f16b95-e2b8-f314-f244-63e862742017@oracle.com> References: <89d512d8-73d4-ad6f-8a5d-5d5bd08adda4@oracle.com> <1011153534.242641.1492462077448.JavaMail.zimbra@u-pem.fr> <97f16b95-e2b8-f314-f244-63e862742017@oracle.com> Message-ID: Some quick background on the putative benefits of this feature (and why we're investing in it): - Lazy constant loading. Currently, constructing a Class, MethodType, or MethodHandle in a forces eager class loading, which (a) contributes to startup and (b) potentially leads to bootstrap loops. Being able to express an LDC from nominal inputs in Java means constant resolution can be delayed with no loss of performance. - Indy. Right now, there's no nonreflective way to encode an indy in Java source code. As we use indy more and more, this becomes an increasingly bigger problem, especially for testing. - Nominal carriers for bytecode and compiler plugin APIs. Bytecode APIs and compiler plugins need a way to describe classfile constants, both on the generation and parsing side. The "live types" (Class, MethodType, MethodHandle) are unsuitable because the classes being described may not be loadable. This forces API designers into ad-hoc encodings, either abusing strings, or defining one-off classes. - Static opt-out of indy-driven language features. As we have more language features that use indy (lambda, string concat, maybe soon switch classifiers), we need a way to opt out of using indy in targeted ways (globally for AOT, locally for bootstrap avoidance). However, we don't want to force the compiler to understand the semantics of each bootstrap; ideally, bootstraps can be expressed in both nominal and "live" forms, and the compiler can choose which it wants. If the types representable in in the constant pool have both live and nominal representations that can be freely converted to each other, this reduces the code duplication we might otherwise encounter in static-izing indy sites. There's a long road to get here, but this is almost certainly part of the solution. From forax at univ-mlv.fr Wed Apr 19 13:55:26 2017 From: forax at univ-mlv.fr (Remi Forax) Date: Wed, 19 Apr 2017 15:55:26 +0200 (CEST) Subject: Published: pattern matching In-Reply-To: <2522150c-03aa-96fd-d9dd-9ec306b33bfd@oracle.com> References: <2522150c-03aa-96fd-d9dd-9ec306b33bfd@oracle.com> Message-ID: <1893697591.1478676.1492610126886.JavaMail.zimbra@u-pem.fr> Thanks Brian, nice summary, obviously, i'm more interested by the other side of this document, the dark side, i.e. how to implement the pattern matching. so i've mostly syntactic comments: - I was wondering if matches can be spelled instanceof ? i.e if the Java grammar can be extended to support (foo instanceof String s) ? - the var pattern: i'm sure you have considered removing the 'var' int eval(Node n) { return exprswitch(n) { case IntNode(i) -> i; case NegNode(v) -> -eval(v); ... }; } but reject it because - from the compiler perspective, you have to wait the typechecking pass (or a pass just before) to know if a symbol 'v' is a parameter or variable access. - if someone write: return exprswitch(n) { case NegNode(n) -> -eval(n); }; n in NegNode(n) is a value and not a variable, which is a nice puzzler. - or it means not supporting constant in patterns. Am i right ? - exhaustiveness, if the class is sealed/closed, i think all data class should be declared into the interface sealed interface Node { data class IntNode(int value) { } data class NegNode(Node node) { } ... } It makes more clear the fact that you can not open the interface Node to add a new subtype and you do not need to declare that each subtype implements the sealed class. It also means that the real name of IntNode is now Node.IntNode (so you can remove the Node suffix !), but you mostly don't care because when you use the exprswitch, you're switching over a Node so the compiler can infer the right name the same way the compiler infer the right name of an enum constant. And more or less like Scala, the compiler can add a bunch of static factory methods so instead of new Mode.IntNode(0), one can write Node.IntNode(0), IntNode being the name of a factory method that creates an IntNode. The astute reader in me see that as a sum type for the compiler and an interface for the VM, i.e. even if the VM do not allow other subtypes than the one listed at runtime, the fact that you do not have to have a default branch should be a JIToptimization, in the bytecode or in a corresponding method handle tree, the default branch should still exist. regards, R?mi ----- Mail original ----- > De: "Brian Goetz" > ?: amber-dev at openjdk.java.net > Envoy?: Mardi 18 Avril 2017 23:34:45 > Objet: Published: pattern matching > I've made public the following document: > > http://cr.openjdk.java.net/~briangoetz/amber/pattern-match.html > > which offers an overview of possible directions for pattern matching in > Java. This is an exploratory document and does not constitute a plan > for any specific feature in any specific version of the Java Language. > Similarly, though it may reference other features under consideration, > this is purely for illustrative purposes. There is no timetable on > which these features might appear, if ever. > > Over the next few weeks, I'm going to begin, slowly, discussing various > aspects of pattern matching on the EG list. From gavin.bierman at oracle.com Wed Apr 19 14:33:14 2017 From: gavin.bierman at oracle.com (Gavin Bierman) Date: Wed, 19 Apr 2017 15:33:14 +0100 Subject: Published: pattern matching In-Reply-To: <1893697591.1478676.1492610126886.JavaMail.zimbra@u-pem.fr> References: <2522150c-03aa-96fd-d9dd-9ec306b33bfd@oracle.com> <1893697591.1478676.1492610126886.JavaMail.zimbra@u-pem.fr> Message-ID: <621F874B-92CB-445A-A30F-9BEFDE903238@oracle.com> > On 19 Apr 2017, at 14:55, Remi Forax wrote: > > Thanks Brian, > nice summary, obviously, i'm more interested by the other side of this document, the dark side, i.e. how to implement the pattern matching. :-) The current pattern matching prototype is decoupled from the more advanced implementation effort. > so i've mostly syntactic comments: > - I was wondering if matches can be spelled instanceof ? > i.e if the Java grammar can be extended to support (foo instanceof String s) ? Possibly, but as a pattern can be a literal value you?d end up allowing (foo instanceof 42). Or, I suppose we could extend instanceof to permit type patterns only. Seems a little ad-hoc though. > - the var pattern: i'm sure you have considered removing the 'var' > int eval(Node n) { > return exprswitch(n) { > case IntNode(i) -> i; > case NegNode(v) -> -eval(v); > ... > }; > } > but reject it because > - from the compiler perspective, you have to wait the typechecking pass (or a pass just before) to know if a symbol 'v' is a parameter or variable access. > - if someone write: > return exprswitch(n) { > case NegNode(n) -> -eval(n); > }; > n in NegNode(n) is a value and not a variable, which is a nice puzzler. > - or it means not supporting constant in patterns. > > Am i right ? Kind of. We are still working through exactly what we think nested patterns mean. But what the document suggests is that the pattern has a set of binding variables (maybe call these the pattern variables) that are bound by the process of pattern matching (possibly by an extractor). So it?s reminiscent of a (very) local declaration; so ?var? seems like the right syntax. Also, as you spotted, it makes for less grammar ambiguity. I don?t think we plan to support equality patterns as you suggest in the NegNode(n) example (if I?m reading it right). If you want that, then you should use a guard: case NegNode(var x) where x == n -> ... Gavin > > - exhaustiveness, > if the class is sealed/closed, i think all data class should be declared into the interface > > sealed interface Node { > data class IntNode(int value) { } > data class NegNode(Node node) { } > ... > } > It makes more clear the fact that you can not open the interface Node to add a new subtype and you do not need to declare that each subtype implements the sealed class. > > It also means that the real name of IntNode is now Node.IntNode (so you can remove the Node suffix !), but you mostly don't care because when you use the exprswitch, you're switching over a Node so the compiler can infer the right name the same way the compiler infer the right name of an enum constant. > > And more or less like Scala, the compiler can add a bunch of static factory methods so instead of new Mode.IntNode(0), one can write Node.IntNode(0), IntNode being the name of a factory method that creates an IntNode. > > The astute reader in me see that as a sum type for the compiler and an interface for the VM, i.e. even if the VM do not allow other subtypes than the one listed at runtime, the fact that you do not have to have a default branch should be a JIToptimization, in the bytecode or in a corresponding method handle tree, the default branch should still exist. > > regards, > R?mi > > ----- Mail original ----- >> De: "Brian Goetz" >> ?: amber-dev at openjdk.java.net >> Envoy?: Mardi 18 Avril 2017 23:34:45 >> Objet: Published: pattern matching > >> I've made public the following document: >> >> http://cr.openjdk.java.net/~briangoetz/amber/pattern-match.html >> >> which offers an overview of possible directions for pattern matching in >> Java. This is an exploratory document and does not constitute a plan >> for any specific feature in any specific version of the Java Language. >> Similarly, though it may reference other features under consideration, >> this is purely for illustrative purposes. There is no timetable on >> which these features might appear, if ever. >> >> Over the next few weeks, I'm going to begin, slowly, discussing various >> aspects of pattern matching on the EG list. From brian.goetz at oracle.com Wed Apr 19 14:34:31 2017 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 19 Apr 2017 10:34:31 -0400 Subject: Published: pattern matching In-Reply-To: <1893697591.1478676.1492610126886.JavaMail.zimbra@u-pem.fr> References: <2522150c-03aa-96fd-d9dd-9ec306b33bfd@oracle.com> <1893697591.1478676.1492610126886.JavaMail.zimbra@u-pem.fr> Message-ID: > so i've mostly syntactic comments: Augh! No! :) > - I was wondering if matches can be spelled instanceof ? This would work if all we were ever going to do was type-test patterns. But when it gets to destructuring patterns, structural patterns, regex patterns, etc -- all reasonable possibilities in the future -- then instanceof starts to feel more strained. But we might consider a loosening of instanceof to accept a binding variable *also*. > - the var pattern: i'm sure you have considered removing the 'var' > int eval(Node n) { > return exprswitch(n) { > case IntNode(i) -> i; > case NegNode(v) -> -eval(v); > ... > }; > } > but reject it because > - from the compiler perspective, you have to wait the typechecking pass (or a pass just before) to know if a symbol 'v' is a parameter or variable access. Yes, we considered it. But the reason for preferring var here is not necessarily just compiler complexity; it's that it's weird for if (x matches Foo(y)) to be a _declaration_ for y. Java developers are not used to that. Having the slightly more verbose if (x matches Foo(int y)) or if (x matches Foo(var y)) makes it more obvious that y is being declared, not used. This seems a good tradeoff of clarity for verbosity. > - exhaustiveness, > if the class is sealed/closed, i think all data class should be declared into the interface > > sealed interface Node { > data class IntNode(int value) { } > data class NegNode(Node node) { } > ... > } > It makes more clear the fact that you can not open the interface Node to add a new subtype and you do not need to declare that each subtype implements the sealed class. This is definitely one of the contenders. Without branching out into the design space of sealing too much -- the mention in the doc was mostly intended to suggest that its on the table -- there's a spectrum of flexibility for sealing (both at the language and VM level). The simplest is that sealing means "nestmates only", as you suggest. Of the candidates, I think this is probably the best choice at the language level. It's on my list (but several entries down) to write up a more detailed summary of our explorations here. > It also means that the real name of IntNode is now Node.IntNode (so you can remove the Node suffix !), but you mostly don't care because when you use the exprswitch, you're switching over a Node so the compiler can infer the right name the same way the compiler infer the right name of an enum constant. Like enums. (BTW, when we get to expression switches, we'll have to tweak our treatment of exhaustiveness for enum switches.) > And more or less like Scala, the compiler can add a bunch of static factory methods so instead of new Mode.IntNode(0), one can write Node.IntNode(0), IntNode being the name of a factory method that creates an IntNode. Possibly. Data classes are a whole separate feature, about which people will have strong opinions... let's come back to that later. From mark at io7m.com Wed Apr 19 14:48:36 2017 From: mark at io7m.com (mark at io7m.com) Date: Wed, 19 Apr 2017 14:48:36 +0000 Subject: Published: pattern matching In-Reply-To: References: <2522150c-03aa-96fd-d9dd-9ec306b33bfd@oracle.com> <1893697591.1478676.1492610126886.JavaMail.zimbra@u-pem.fr> Message-ID: <20170419144836.3be41203@copperhead.int.arc7.info> On 2017-04-19T10:34:31 -0400 Brian Goetz wrote: > > Yes, we considered it. But the reason for preferring var here is not > necessarily just compiler complexity; it's that it's weird for > > if (x matches Foo(y)) > > to be a _declaration_ for y. Java developers are not used to that. Strongly agree here. I've taken a few programmers with no Haskell experience through introductions to Haskell and one thing that reliably trips them up is that patterns introduce bindings in a way that's not totally explicit. f :: Nat -> Nat f Zero = ... f (Succ z) = ... "Where is this z variable declared?! Oh, right..." M From forax at univ-mlv.fr Wed Apr 19 15:00:04 2017 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Wed, 19 Apr 2017 17:00:04 +0200 (CEST) Subject: Published: pattern matching In-Reply-To: <20170419144836.3be41203@copperhead.int.arc7.info> References: <2522150c-03aa-96fd-d9dd-9ec306b33bfd@oracle.com> <1893697591.1478676.1492610126886.JavaMail.zimbra@u-pem.fr> <20170419144836.3be41203@copperhead.int.arc7.info> Message-ID: <1950928569.1516399.1492614004117.JavaMail.zimbra@u-pem.fr> ----- Mail original ----- > De: mark at io7m.com > ?: "Brian Goetz" > Cc: "Remi Forax" , "amber-spec-experts" > Envoy?: Mercredi 19 Avril 2017 16:48:36 > Objet: Re: Published: pattern matching > On 2017-04-19T10:34:31 -0400 > Brian Goetz wrote: >> >> Yes, we considered it. But the reason for preferring var here is not >> necessarily just compiler complexity; it's that it's weird for >> >> if (x matches Foo(y)) >> >> to be a _declaration_ for y. Java developers are not used to that. > > Strongly agree here. I've taken a few programmers with no Haskell > experience through introductions to Haskell and one thing that reliably > trips them up is that patterns introduce bindings in a way that's not > totally explicit. > > f :: Nat -> Nat > f Zero = ... > f (Succ z) = ... > > "Where is this z variable declared?! Oh, right..." > > M while i do not disagree, making things explicit is usually a win, i see the same issue with my student the first time they see the lambda syntax (z) -> ... given that the proposed syntax for the pattern matching also uses '->', it maybe less hard. R?mi From forax at univ-mlv.fr Wed Apr 19 15:08:47 2017 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Wed, 19 Apr 2017 17:08:47 +0200 (CEST) Subject: Published: pattern matching In-Reply-To: <621F874B-92CB-445A-A30F-9BEFDE903238@oracle.com> References: <2522150c-03aa-96fd-d9dd-9ec306b33bfd@oracle.com> <1893697591.1478676.1492610126886.JavaMail.zimbra@u-pem.fr> <621F874B-92CB-445A-A30F-9BEFDE903238@oracle.com> Message-ID: <271498551.1521629.1492614527942.JavaMail.zimbra@u-pem.fr> ----- Mail original ----- > De: "Gavin Bierman" > ?: "Remi Forax" > Cc: "Brian Goetz" , "amber-spec-experts" > Envoy?: Mercredi 19 Avril 2017 16:33:14 > Objet: Re: Published: pattern matching >> On 19 Apr 2017, at 14:55, Remi Forax wrote: >> >> Thanks Brian, >> nice summary, obviously, i'm more interested by the other side of this document, >> the dark side, i.e. how to implement the pattern matching. > > :-) The current pattern matching prototype is decoupled from the more advanced > implementation effort. from my own experience, i've added new features in the language when i see how easy it was to implement them when i had the first cut of the implementation. > > >> so i've mostly syntactic comments: >> - I was wondering if matches can be spelled instanceof ? >> i.e if the Java grammar can be extended to support (foo instanceof String s) ? > > Possibly, but as a pattern can be a literal value you?d end up allowing (foo > instanceof 42). Or, I suppose we could extend instanceof to permit type > patterns only. Seems a little ad-hoc though. > >> - the var pattern: i'm sure you have considered removing the 'var' >> int eval(Node n) { >> return exprswitch(n) { >> case IntNode(i) -> i; >> case NegNode(v) -> -eval(v); >> ... >> }; >> } >> but reject it because >> - from the compiler perspective, you have to wait the typechecking pass (or a >> pass just before) to know if a symbol 'v' is a parameter or variable access. >> - if someone write: >> return exprswitch(n) { >> case NegNode(n) -> -eval(n); >> }; >> n in NegNode(n) is a value and not a variable, which is a nice puzzler. >> - or it means not supporting constant in patterns. >> >> Am i right ? > > Kind of. We are still working through exactly what we think nested patterns > mean. But what the document suggests is that the pattern has a set of binding > variables (maybe call these the pattern variables) that are bound by the > process of pattern matching (possibly by an extractor). So it?s reminiscent of > a (very) local declaration; so ?var? seems like the right syntax. Also, as you > spotted, it makes for less grammar ambiguity. > > I don?t think we plan to support equality patterns as you suggest in the > NegNode(n) example (if I?m reading it right). If you want that, then you should > use a guard: > > case NegNode(var x) where x == n -> ... yes, local of the surrounding context can only be used in the action but not in the pattern ? and guards are ugly, people write code like this: case A(Long(Long(Complicated(Pattern(var x))))) where x == n -> action1() case A(Long(Long(Complicated(Pattern(var x))))) where x != n -> action2() instead of case A(Long(Long(Complicated(Pattern(var x))))) -> x == n? action1(): action2() > > > Gavin R?mi From forax at univ-mlv.fr Wed Apr 19 15:10:06 2017 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Wed, 19 Apr 2017 17:10:06 +0200 (CEST) Subject: Published: pattern matching In-Reply-To: References: <2522150c-03aa-96fd-d9dd-9ec306b33bfd@oracle.com> <1893697591.1478676.1492610126886.JavaMail.zimbra@u-pem.fr> Message-ID: <1966927211.1522256.1492614606247.JavaMail.zimbra@u-pem.fr> ----- Mail original ----- > De: "Brian Goetz" > ?: "Remi Forax" > Cc: "amber-spec-experts" > Envoy?: Mercredi 19 Avril 2017 16:34:31 > Objet: Re: Published: pattern matching >> so i've mostly syntactic comments: > > Augh! No! :) > >> - I was wondering if matches can be spelled instanceof ? > > This would work if all we were ever going to do was type-test patterns. > But when it gets to destructuring patterns, structural patterns, regex > patterns, etc -- all reasonable possibilities in the future -- then > instanceof starts to feel more strained. > > But we might consider a loosening of instanceof to accept a binding > variable *also*. > >> - the var pattern: i'm sure you have considered removing the 'var' >> int eval(Node n) { >> return exprswitch(n) { >> case IntNode(i) -> i; >> case NegNode(v) -> -eval(v); >> ... >> }; >> } >> but reject it because >> - from the compiler perspective, you have to wait the typechecking pass (or a >> pass just before) to know if a symbol 'v' is a parameter or variable access. > > Yes, we considered it. But the reason for preferring var here is not > necessarily just compiler complexity; it's that it's weird for > > if (x matches Foo(y)) > > to be a _declaration_ for y. Java developers are not used to that. > Having the slightly more verbose > > if (x matches Foo(int y)) > > or > > if (x matches Foo(var y)) > > makes it more obvious that y is being declared, not used. This seems a > good tradeoff of clarity for verbosity. > >> - exhaustiveness, >> if the class is sealed/closed, i think all data class should be declared into >> the interface >> >> sealed interface Node { >> data class IntNode(int value) { } >> data class NegNode(Node node) { } >> ... >> } >> It makes more clear the fact that you can not open the interface Node to add a >> new subtype and you do not need to declare that each subtype implements the >> sealed class. > > This is definitely one of the contenders. Without branching out into > the design space of sealing too much -- the mention in the doc was > mostly intended to suggest that its on the table -- there's a spectrum > of flexibility for sealing (both at the language and VM level). The > simplest is that sealing means "nestmates only", as you suggest. Of the > candidates, I think this is probably the best choice at the language > level. It's on my list (but several entries down) to write up a more > detailed summary of our explorations here. > >> It also means that the real name of IntNode is now Node.IntNode (so you can >> remove the Node suffix !), but you mostly don't care because when you use the >> exprswitch, you're switching over a Node so the compiler can infer the right >> name the same way the compiler infer the right name of an enum constant. > > Like enums. (BTW, when we get to expression switches, we'll have to > tweak our treatment of exhaustiveness for enum switches.) >> And more or less like Scala, the compiler can add a bunch of static factory >> methods so instead of new Mode.IntNode(0), one can write Node.IntNode(0), >> IntNode being the name of a factory method that creates an IntNode. > > Possibly. Data classes are a whole separate feature, about which people > will have strong opinions... let's come back to that later. Ok ! R?mi From brian.goetz at oracle.com Thu Apr 20 18:46:31 2017 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 20 Apr 2017 14:46:31 -0400 Subject: Pattern matching -- background and design goals Message-ID: <11a5ea69-02b1-e0fc-929a-1256a9cb3086@oracle.com> I would like to start the discussion surrounding pattern matching with some motivation and goals. The design space here is enormous, and connected to so many other features, so bear with me as I try to linearize the story. I've posted a general introductory document on possibilities for pattern matching in Java here: http://cr.openjdk.java.net/~briangoetz/amber/pattern-match.html The first question is why, as in: Why is this feature needed in Java at all? I'll start with the obvious reasons (explicated in greater detail in the document): the common pattern of testing an input against multiple candidates not only suffers from boilerplate, but, more importantly, the tools we have for this give bugs too many places to hide, obfuscate the essential business logic, and introduce accidental ordering constraints that make programs slower than necessary. There are also some less obvious reasons why this makes sense. What's being proposed here are really two interrelated features here: better support for compositional test-and-extract (destructuring), and better support for handling do-exactly-one-of-these constructs. While either could stand on its own, the two together are much stronger. Of the two, destructuring is the richer concept. To illustrate why destructuring is so important, allow me to make a comparison to Lambda. Adding the ability to pass behavior as data meant that we could build richer, more powerful libraries. We could move responsibility for control flow from client code (external iteration) to libraries (internal iteration), enabling libraries to expose richer, higher-level operations. Additionally, by exposing higher-level operations, libraries might not have to expose fiddly, state-dependent low-level operations like Iterator.hasNext() / next(), and therefore don't need to code iteration logic against being called in the wrong order or in the wrong state. Destructuring has a similar abstraction-raising characteristic. Consider the methods Class.isArray() and Class.getComponentType(); the latter only returns a value if the former returns true. Method pairs like this offer the worst of both worlds; the client has to make two calls, and has a chance to do it wrong -- and the library has to defend against the client getting it wrong. These two methods really should be one operation, which simplifies both the client invocation and the library implementation: if (c matches Class.arrayClass(Class componentType)) { ... } Further, decomposition (when done right), is compositional, enabling us to express a compound test+extract operation as a single operation, rather than as a sequence of operations, each of which can individually fail. This brings client code more in line with how the problem statement is typically stated. Finally, this is a feature with depth; we can start with simple patterns and simple pattern-aware language constructs, and add more sophisticated kinds of patterns and more constructs that can use patterns as we go. This in turn means a greater return on conceptual complexity. If one looked only at the first few examples in the document, one might be tempted to ask "Why not 'just' do flow typing"? Flow typing would eliminate the (irritating) need to cast something to X right after having done an instanceof test against X. It seems like a cheap win (though would invariably put pressure on our handling of intersection types), but it just doesn't go very far -- essentially it eliminates a single increment of boilerplate and a single place where error can creep in. This is not nothing, and it's a defensible choice, but it seems like it would be a missed opportunity. Similarly, one might ask "Why not 'just' do type switch?". Again, this is a feature that seems merely "additive" rather than multiplicative; it again eliminates some boilerplate in repeated type tests, but it doesn't go much farther than that. Which brings me around to a deeper observation, which we think is at the design center of this feature: destructuring is the natural dual of aggregation, and an obvious (and yet missing) component of object-oriented modeling. People often associate pattern-matching as being a "functional programming" feature. But any language that supports aggregation (that is, all of them) also has to address how aggregates are going to be decomposed. Scala moved the ball forward here by showing that having a destructuring operator ("unapply") on objects allows us to match and destructure objects in terms of what their constructor invocation looks like (regardless of their representation.) This is not "OO borrows FP idea", this is "OO applies sensible programming concept in a natural OO way." That said, I think Scala didn't go far enough. (No criticism intended; they moved the ball forward dramatically, there's just farther to go.) For what are likely mostly accidental reasons, Scala only allows a single constructor pattern per class, even though constructors themselves can be overloaded. Again, without criticism: I'm guessing most of the motivation for Scala's pattern matching was drawn from the distinguished and well-behaved case of algebraic data types, not general objects. So the limitation of a single unapply was not really a problem; algebraic data types don't go out of their way to hide or evolve their representation. But we can apply destructuring to a wider range of targets, in a more OO way. So, to put a stake in the ground, we believe that: - destructuring is the dual of construction, and should be treated as a first-class construct; - destructuring an object should be syntactically similar to how that object is constructed. From these principles, it follows that: - if you can declare a ctor, you should be able to declare an instance dtor; - instance dtors should support the same overloading and inheritance behaviors as ctors; - if you can declare a static factory, you should be able to declare a static dtor. (The syntax of how dtors are declared is a topic for another day.) If an object is created with a constructor: x = new Foo(a, b) ideally, it should be destructurable with an instance dtor: if (x matches Foo(var a, var b)) Note the syntactic similarity; this is not accidental. Acquiring dtors isn't automatic (though data classes can automatically acquire both ctors and dtors), but it should be easy and straightforward to declare such a dtor. If dtors are overloaded, type information at the use site may be needed to disambiguate. So if we provide dtors Foo(int y) and Foo(String y), then if (x matches Foo(var y)) is ambiguous, but by replacing the var-pattern with a an explicit type-test pattern, the compiler can properly select: if (x matches Foo(int x)) Similarly, if an object is created with a static factory: x = Foo.of(a, b) it should be destructurable with static dtors: if (x matches Foo.of(var a, var b)) So, for example, since Optional has two factories: x = Optional.of(y) x = Optional.empty() one can discriminate via: case Optional.of(var y): case Optional.empty(): Why does this matter? By way of example, we often prefer factory methods to constructors. A factory has more flexibility than a ctor; it can return different subtypes in different conditions, and it can change its implementation over time to return different subtypes. That a factory commits only to an abstract type: static Foo redFoo() { return new MyPrivateFooImplementation(); } is great for the implementor, but less so for the client -- once the caller gets a Foo from the factory, it has no way of asking "is this a red Foo?", unless we clutter the API with ad-hoc methods like isRedFoo(). This means that APIs with multiple factories have some bad choices: either live with the opacity, which can limit what the client can do (sometimes this is good, but sometimes it's not), add lots of new API points for destructuring, or expose the intermediate types. Having a complementary dtor allows the implementation to hide its details while still providing a degree of reversibility. Without venturing near the toxic syntax bikeshed: static ... matcher ... Foo ... redFoo(...) { ... } the library author can render the factory reversible without exposing the implementation details. x = redFoo(); ... if (x matches redFoo()) { ... } The implementation of redFoo() remains hidden, but because the dtor is part of the same library as the factory, it can still help a client reconstruct the object's state (or the parts that it wants to), without compromising the encapsulation. (In just the last few weeks of thinking about this, once you start to see this pattern, you can't look at an API and not see it.) Again, this is mostly something to file in the background and think about the consequences of -- until we start to circulate a JEP for pattern matching, this is still technically outside the Amber charter. -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Thu Apr 20 18:53:56 2017 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 20 Apr 2017 14:53:56 -0400 Subject: Rescuing switch, AND and OR patterns, guards, and continue Message-ID: <9adeac3b-bd3d-3e77-604b-7f708cba6c74@oracle.com> Since everybody's been pestering me with questions offline, I've dropped another document that outlines our thinking to date on how we'd extend switch to support patterns (in both expression and statement form.) http://cr.openjdk.java.net/~briangoetz/amber/switch-rehab.html Warning: it's rough. Again, mostly to put the ideas in your head, and let them stew there for a while. From mark at io7m.com Sat Apr 22 18:25:37 2017 From: mark at io7m.com (mark) Date: Sat, 22 Apr 2017 18:25:37 +0000 Subject: Pattern matching -- background and design goals In-Reply-To: <11a5ea69-02b1-e0fc-929a-1256a9cb3086@oracle.com> References: <11a5ea69-02b1-e0fc-929a-1256a9cb3086@oracle.com> Message-ID: <20170422182537.11098bba@copperhead.int.arc7.info> On 2017-04-20T14:46:31 -0400 Brian Goetz wrote: > I would like to start the discussion surrounding pattern matching with > some motivation and goals. The design space here is enormous, and > connected to so many other features, so bear with me as I try to > linearize the story. I've posted a general introductory document on > possibilities for pattern matching in Java here: > > http://cr.openjdk.java.net/~briangoetz/amber/pattern-match.html > Having digested both this and the switch document, I'm slightly disappointed in that I can't find much to which I object. :) The new scoping rules are a little spooky with regards to reading comprehension, but I think they're a fairly pragmatic choice in the absence of "let" expressions in the language. I'm curious though, based on the given Optional example, if the intention is to design things in such a way that values of the existing Optional class could be matched upon (with exhaustiveness checks) without anyone having edited the definition of Optional? I remember hearing in one of your previous talks that it's a design goal to at least allow Optional and similar types to be updated in a way that's backwards compatible with the old definitions. I'm not exactly sure what this means. M From brian.goetz at oracle.com Sat Apr 22 19:04:20 2017 From: brian.goetz at oracle.com (Brian Goetz) Date: Sat, 22 Apr 2017 15:04:20 -0400 Subject: Pattern matching -- background and design goals In-Reply-To: <20170422182537.11098bba@copperhead.int.arc7.info> References: <11a5ea69-02b1-e0fc-929a-1256a9cb3086@oracle.com> <20170422182537.11098bba@copperhead.int.arc7.info> Message-ID: <06abedd2-f798-5a4c-e868-990319efcc42@oracle.com> > Having digested both this and the switch document, I'm slightly > disappointed in that I can't find much to which I object. :) Patience! I'm sure I'll come up with something objectionable eventually. > The new scoping rules are a little spooky with regards to reading > comprehension, but I think they're a fairly pragmatic choice in the > absence of "let" expressions in the language. The secret to understanding these rules is that they are the definite-assignment rules in disguise. They basically say that a binding variable is made-available-to any expression / statement in which it would be definitely assigned. The weird thing about them is that they don't conform to traditional block scoping; the set of expressions/statements into which a given pattern may inject bindings does not form a traditional block. You could call this "flow-based scoping." > I'm curious though, based on the given Optional example, if the > intention is to design things in such a way that values of the existing > Optional class could be matched upon (with exhaustiveness checks) > without anyone having edited the definition of Optional? Not quite. But it doesn't require deep intrusion (like changing the implementation to a Some/None decomposition); merely the adding of matchers (which I claim are some sort of class member, so this is like adding new methods) for Optional.of() and Optional.empty(). What that doesn't give us is exhaustiveness -- but at least it gives us a path to back-fitting existing classes with ctors/factories to play in destructuring-land without major surgery. From mark at io7m.com Sat Apr 22 21:02:45 2017 From: mark at io7m.com (mark) Date: Sat, 22 Apr 2017 21:02:45 +0000 Subject: Pattern matching -- background and design goals In-Reply-To: <06abedd2-f798-5a4c-e868-990319efcc42@oracle.com> References: <11a5ea69-02b1-e0fc-929a-1256a9cb3086@oracle.com> <20170422182537.11098bba@copperhead.int.arc7.info> <06abedd2-f798-5a4c-e868-990319efcc42@oracle.com> Message-ID: <20170422210245.735f8442@copperhead.int.arc7.info> On 2017-04-22T15:04:20 -0400 Brian Goetz wrote: > > The weird thing about them is > that they don't conform to traditional block scoping; the set of > expressions/statements into which a given pattern may inject bindings > does not form a traditional block. You could call this "flow-based > scoping." Yeah, that's the spooky part. I have a strong preference for simple lexical scoping, but I'll suspend any disbelief until I can play with an implementation. > > I'm curious though, based on the given Optional example, if the > > intention is to design things in such a way that values of the existing > > Optional class could be matched upon (with exhaustiveness checks) > > without anyone having edited the definition of Optional? > > Not quite. But it doesn't require deep intrusion (like changing the > implementation to a Some/None decomposition); merely the adding of > matchers (which I claim are some sort of class member, so this is like > adding new methods) for Optional.of() and Optional.empty(). What that > doesn't give us is exhaustiveness -- but at least it gives us a path to > back-fitting existing classes with ctors/factories to play in > destructuring-land without major surgery. Got it. I think it'd be slightly tragic if Optional couldn't get exhaustiveness in some form eventually. I mean in languages like Kotlin and Scala (as you're probably well aware) Option{al} is sort of intended to subsume nullable references for the purposes of correctness ("You're prevented from failing to check for a missing value"). It's pretty much ADTs 101, so it'd be sad if Java suddenly got pattern matching and exhaustiveness checks but ended up with an Optional that couldn't make good use of them. M From brian.goetz at oracle.com Wed Apr 26 21:46:24 2017 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 26 Apr 2017 17:46:24 -0400 Subject: Fwd: JEP 303: Intrinsics for the LDC and INVOKEDYNAMIC Instructions In-Reply-To: References: Message-ID: The following was received through the valhalla-spec-comments mailbox. -------- Forwarded Message -------- Subject: JEP 303: Intrinsics for the LDC and INVOKEDYNAMIC Instructions Date: Wed, 26 Apr 2017 23:24:40 +0200 From: Jeremy Barrow To: valhalla-spec-comments at openjdk.java.net I'll preface this with: I know that making a language construct for this has already been set as an explicit non-goal. If you're 100% set on that, then you can probably skip this email, but if you're interested in the opinions of some random who had a free afternoon, then read on. For the same reason that collection literals are a bad idea within the language; compiler intrinsics are a bad idea within the library. Assuming a class gets added to the standard library, how would other languages react to that? Are they going to have to implement some support for it, or instead explain to their userbase that some portion of the library is redundant, more so if the language already supports language level intrinsics. This would be the perfect time to finally use "const". const MethodType type = ...; (Side-note: this might start dipping into ConstantDynamic (condyn) territory, but bear with me.) The idea would allow the programmer to declare a field or a local variable with the const modifier. When the programmer does this, it should be treated as a compile time constant. As such, it doesn't make sense to be able to be able to declare a const field, as a static final const field. This might affect readability, and should probably be reviewed, but semantically, it makes sense. If the field/variable is used in a non-const context, then it should be treated as if it was written there. (Excluding some types, such as strings, classes, etc) Because it's a compile time constant, it raises some questions: Should the source code contain something that won't be present in the final runtime class? I think it makes sense, as it's not the only thing that java can omit from the runtime class. Annotations can have a non-runtime retention, so I don't think the concept is a shock to java programmers, and arguably, a field/local variable with const is easier to spot than a non-runtime annotation (because you have to inspect the annotation's class). I was originally going to also suggest maybe having support for const methods as well, and variables and parameters can only be const, etc, but I think that's far too much for this simple API, and would play very well into condyn's implementation. Should the compiler be intrinsically aware of the type, or be able to be made aware? (Intrinsic types or some way to communicate to the compiler how to convert a type into a constant) This is dangerously close to condyn, but I felt the need to bring it up just to show how well the two could go together. With this API, I think, initially, intrinsic types are perfect. Higher level types that can be known to the compiler, such as MethodType, Class, String, are more or less perfect for it. Now, the main problem: You can't declare a method with those constants without an intrinsic type, or if you could, it might not be too readable. That being said, I may have overlooked a nice way to declare methods with constants. The first solution that came to mind was representing MethodHandle as a const. Currently, there's no explicit creation mechanism, outside of the Lookup system itself, for obvious reasons. Should there be a const factory method for MethodHandles, that can only accept const parameters? We have method references, could we use them? I spent some time thinking about this, and it almost always boiled down to an intrinsic class. As even if we can define a MethodHandle in a const manner, we still have to deal with the compiler needing to be aware of the point where it needs to emit the invokedynamic instruction. In effect, const is a way to define the constants, not the instructions. I think that was the shortcoming, and having something at the language level for bytecode specific instructions is a potentially toxic idea. Now, all that being said, and the final result is in more or less the exact same position as before, I have suggested using const. Which means we can declare new semantics. What if we could define classes that _have_ to be entirely constant? What if we could attach a @Retention to it, or define a RetentionPolicy for it? This means the JVM could toss the class, much like it does annotations. This could make the way for a whole section of programmable constants later on with condyn. I will admit, this got much bigger than I originally planned, but hopefully, I got across my point, if I failed, I at least hope that I gave someone a good idea to work it. Thanks for reading this wall of text. -------------- next part -------------- An HTML attachment was scrubbed... URL: From daniel.smith at oracle.com Thu Apr 27 19:29:21 2017 From: daniel.smith at oracle.com (Dan Smith) Date: Thu, 27 Apr 2017 13:29:21 -0600 Subject: Spec draft for JEP 301 Enhanced Enums Message-ID: <72CC1D39-C53F-4732-95A8-307173E985BD@oracle.com> Hello, all. Please see the following spec draft for JEP 301 Enhanced Enums. http://cr.openjdk.java.net/~dlsmith/enhanced-enums.html Here's the JEP description, for context: http://openjdk.java.net/jeps/301 This feature is fairly straightforward, with some care necessary to ensure proper compatibility behavior. Thanks, Dan From daniel.smith at oracle.com Thu Apr 27 19:47:08 2017 From: daniel.smith at oracle.com (Dan Smith) Date: Thu, 27 Apr 2017 13:47:08 -0600 Subject: Spec draft for JEP 302 Lambda Leftovers Message-ID: JEP 302 proposes 3 loosely-related features: unnamed parameters, allowing shadowing within lambdas, and improvements to overload resolution. http://openjdk.java.net/jeps/302 The third feature is still a work in progress, and we may choose not to pursue it. The following spec changes allow for the first two features, unnamed parameters and shadowing within lambdas: http://cr.openjdk.java.net/~dlsmith/lambda-leftovers.html There are different ways to treat "_" as a parameter name. The approach I've chosen is to make the name of a parameter an optional property. Parameters without names have no scope, and thus can't conflict with each other. Thanks, Dan