some experiments with Concise Method Bodies
forax at univ-mlv.fr
forax at univ-mlv.fr
Mon Oct 15 21:32:13 UTC 2018
Your argument "statement need braces" is also valid for a statement switch that using the arrow syntax, but currently, "-> throw" is a valid syntax for such switch.
With my teacher hat, i value far more consistency in term of syntax than some rationale depending on the semantics.
Rémi
----- Mail original -----
> De: "Brian Goetz" <brian.goetz at oracle.com>
> À: "Remi Forax" <forax at univ-mlv.fr>
> Cc: "Aaron Scott-Boddendijk" <talden at gmail.com>, "Stuart Marks" <stuart.marks at oracle.com>, "amber-dev"
> <amber-dev at openjdk.java.net>
> Envoyé: Lundi 15 Octobre 2018 22:55:20
> Objet: Re: some experiments with Concise Method Bodies
> For lambdas, is the difference between
>
> () -> { throw E; }
> and
> () -> throw E;
>
> which seems pretty harmless, but probably isn't worth much either. For
> CMBs (bringing this back around to on-topic!), let's consider other
> statement forms too, such as setters. I think drawing the line at
> "statements needs braces" is probably a cleaner way to draw it. (Note
> that methods whose bodies are a single, non-return statement gain even
> less out of CMB, because there's no "return" to be elided.)
>
> On 10/15/2018 4:36 PM, Remi Forax wrote:
>> while throw is not an expression,
>> currently the syntax "-> throw" is allowed in an expression switch but not
>> allowed neither in a lambda and nor as a concise method body.
>>
>> I believe we should fix these discrepancies by allowing -> throw as part of a
>> lambda or a CMB.
>>
>> Rémi
>>
>> ----- Mail original -----
>>> De: "Brian Goetz" <brian.goetz at oracle.com>
>>> À: "Aaron Scott-Boddendijk" <talden at gmail.com>, "Stuart Marks"
>>> <stuart.marks at oracle.com>
>>> Cc: "amber-dev" <amber-dev at openjdk.java.net>
>>> Envoyé: Lundi 15 Octobre 2018 22:17:44
>>> Objet: Re: some experiments with Concise Method Bodies
>>> Not surprisingly, we explored this ground, and decided we liked things
>>> where they are.
>>>
>>> Your "instead of" example makes the alternative look worse than it is.
>>> You could instead say:
>>>
>>> if (!condition)
>>> throw new BadStuffException();
>>> final Result result = goodPath();
>>>
>>> which isn't nearly as fussy, and is more clear, as it puts your preconditions up
>>> front.
>>>
>>> In the run-up to switch expressions, we explored allowing `throw` to be an
>>> expression, but that would lead to code for which the control flow is much
>>> harder to read:
>>>
>>> if (isFoo(x) || isBar(y) || throw new NotFooExpression()) { ... }
>>>
>>> or
>>>
>>> if (methodWithSideEffect(x) && throw new UnrelatedException()) { /* dead */ }
>>>
>>> or, even
>>>
>>> m(3, 4, throw new XYZException(...))
>>>
>>> Now, you could say "Sure, I don't want all that, but I want it for ternary", and
>>> that would be a reasonable opinion, but at this point the cost-benefit of the
>>> incremental complexity starts to tilt the other way. Refactoring a ternary to
>>> an if, as above, really isn't so bad -- and it is more clear. So we decided to
>>> not create a special case for this.
>>>
>>>
>>>
>>>
>>> On 10/15/2018 3:32 PM, Aaron Scott-Boddendijk wrote:
>>>>> public boolean add(E e) -> throw new UnsupportedOperationException();
>>>> This is similar to issues with flow-control. It would be nice to be able
>>>> to do..
>>>>
>>>> final Result r = condition ? goodPath() : throw new BadStuffException();
>>>> ...
>>>>
>>>> instead of...
>>>>
>>>> final Result;
>>>> if (condition) {
>>>> result = goodPath();
>>>> } else {
>>>> throw new BadStuffException();
>>>> }
>>>> ...
>>>>
>>>> Either branch of the ternary should be allowed to be a throw and, if both
>>>> are throws the ternary is a Void expression (allowing for a concise 'which
>>>> exception are we throwing').
>>>>
>>>> --
>>>> Aaron Scott-Boddendijk
>>>>
>>>>
>>>> On Tue, Oct 16, 2018 at 7:40 AM Stuart Marks <stuart.marks at oracle.com>
>>>> wrote:
>>>>
>>>>> When I first saw the CMB proposal, I immediately thought "delegation" and
>>>>> so I
>>>>> thought it would be useful to see how CMB could be applied in this area.
>>>>> The
>>>>> example I chose was the implementation class for
>>>>> Collections.unmodifiableCollection(), which returns an instance of a
>>>>> wrapper
>>>>> class [1] that delegates most (but not all) method calls to the backing
>>>>> collection.
>>>>>
>>>>> I cloned the amber repo and updated to the concise-method-declarations
>>>>> branch.
>>>>> Building the JDK from this branch worked smoothly. Compiling "Hello,
>>>>> world" worked:
>>>>>
>>>>> public class Concise {
>>>>> public static void main(String[] args)
>>>>> -> System.out.println("Hello, concise method bodies!");
>>>>> }
>>>>>
>>>>> I took Collections$UnmodifiableCollection and extracted it into a
>>>>> standalone
>>>>> class UnmodColl1.java [2] and stripped out some extraneous stuff. I then
>>>>> copied
>>>>> it to UnmodColl2.java [3] and converted it to use CMB. Along the way I
>>>>> made some
>>>>> shortening transformations that didn't directly relate to CMB, so I
>>>>> retrofitted
>>>>> them back to UnmodColl1.java. The results are more-or-less diff-able.
>>>>>
>>>>> Observations:
>>>>>
>>>>> * I was able to use CMB for almost everything. These wrapper classes
>>>>> consistent
>>>>> almost entirely of one-liners, to which CMB can be directly applied.
>>>>>
>>>>> * The CMB version is a bit prettier. Lack of 'return' and braces reduces
>>>>> visual
>>>>> clutter, and in a few cases it enabled things to be moved onto a single
>>>>> line,
>>>>> reducing vertical space.
>>>>>
>>>>> * Note that these wrapper classes already bend the usual style rules for
>>>>> braces
>>>>> and statements of a method body, e.g., even in the original we have the
>>>>> method
>>>>>
>>>>> public int size() {return c.size();}
>>>>>
>>>>> written on a single line. If the style were followed strictly, this would
>>>>> be
>>>>> written on three lines. There's a reason for this; writing all these
>>>>> one-liners
>>>>> in standard style would waste an egregious amount of vertical space. CMB
>>>>> syntax
>>>>> provides a much bigger win over standard style than over the non-standard,
>>>>> compact style used in these Collections wrapper classes.
>>>>>
>>>>> On the other hand, while the non-standard, compact style does save
>>>>> vertical
>>>>> space, it's kind of annoying to deal with, because it's non-standard. In
>>>>> my
>>>>> opinion the tradeoff is in favor of the compact style. But CMB mostly
>>>>> relieves
>>>>> us of having to make this tradeoff at all.
>>>>>
>>>>> * None of these methods have javadoc, since this is a private
>>>>> implementation class.
>>>>>
>>>>> * Many of the delegating methods can be written using the method reference
>>>>> form:
>>>>>
>>>>> public int size() = c::size;
>>>>>
>>>>> This would produce a marginal improvement in the syntax, mostly by
>>>>> removing the
>>>>> need for a set of parens and the need to pass parameters explicitly. The
>>>>> issue
>>>>> of time-of-evaluation of the receiver mostly doesn't arise here, since the
>>>>> delegate is a final field initialized by the constructor.
>>>>>
>>>>> * I tried to use CMB for the constructor, but I got an error message
>>>>> saying it
>>>>> was disallowed. No great loss, and makes some sense, I guess.
>>>>>
>>>>> * Many of the methods throw exceptions. I had a bit of a wrestling match
>>>>> with
>>>>> this. My first attempt was
>>>>>
>>>>> UnsupportedOperationException uoe() -> new
>>>>> UnsupportedOperationException;
>>>>> public boolean add(E e) -> throw uoe();
>>>>>
>>>>> But this doesn't work, because 'throw' isn't an expression, and the
>>>>> concise body
>>>>> is required to be an expression of the right type even though we know the
>>>>> method
>>>>> can never return normally. Of course, one can do this:
>>>>>
>>>>> public boolean add(E e) { throw uoe(); }
>>>>>
>>>>> but I wanted to use CMB. My next attempt was this:
>>>>>
>>>>> boolean throwUOE() { throw new UnsupportedOperationException(); }
>>>>> public boolean add(E e) -> throwUOE();
>>>>>
>>>>> Now this works, but it's a hack. Most of the throwing methods happen to
>>>>> return
>>>>> boolean, so I was able to declare the helper method to return boolean as
>>>>> well.
>>>>> It can also be used for void-returning methods. But if I had needed to
>>>>> delegate
>>>>> several throwing methods that had different return types, I wouldn't be
>>>>> able to
>>>>> do this.
>>>>>
>>>>> I tried to use the method reference form to deal with the throwing
>>>>> methods, but
>>>>> that didn't work, since those methods all take different parameters.
>>>>>
>>>>> It would be nice if I could just do
>>>>>
>>>>> public boolean add(E e) -> throw new UnsupportedOperationException();
>>>>>
>>>>> or
>>>>>
>>>>> public boolean add(E e) -> throw uoe();
>>>>>
>>>>> where uoe() returns an exception instance.
>>>>>
>>>>> **
>>>>>
>>>>> Summary: it was fun playing with this feature. It seems to clean things up
>>>>> a
>>>>> little bit, and for classes that are all one-liner methods the little
>>>>> savings
>>>>> add up to a lot. The savings are incrementally greater compared to a
>>>>> hypothetical wrapper class that uses the standard coding style. CMB
>>>>> doesn't seem
>>>>> to be a must-have feature, at least not yet, but it does seem to have some
>>>>> potential.
>>>>>
>>>>> s'marks
>>>>>
>>>>>
>>>>> [1]
>>>>>
>>>>> http://hg.openjdk.java.net/jdk/jdk11/file/1ddf9a99e4ad/src/java.base/share/classes/java/util/Collections.java#l1021
>>>>>
>>>>> [2] http://cr.openjdk.java.net/~smarks/amber/UnmodColl1.java
>>>>>
>>>>> [3] http://cr.openjdk.java.net/~smarks/amber/UnmodColl2.java
More information about the amber-dev
mailing list