some experiments with Concise Method Bodies

Stuart Marks stuart.marks at oracle.com
Mon Oct 15 18:39:49 UTC 2018


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