some experiments with Concise Method Bodies
Brian Goetz
brian.goetz at oracle.com
Mon Oct 15 20:55:20 UTC 2018
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