Ergonomic Issues with ARM blocks
Neal Gafter
neal at gafter.com
Sun Mar 29 11:06:45 PDT 2009
Here are some notes on ergonomic (usability) issues I found during my
attempt to retrofit some code with ARM blocks based on its latest
spec.
(1) "try" is not a natural keyword for the intended semantics.
The use of the try keyword for this construct is natural while
migrating existing code, because the existing solution already
requires a try-finally statement. But it is a non sequitur (in some
cases shockingly inappropriate) when reading code or when writing new
code. The problem is that it calls attention to the exceptional case
rather than the normal case, distracting the reader from the flow of
the business logic. That is the very problem this construct should be
designed to solve. This violates the principle that a language
construct should be designed to integrate well with the language as it
appears with the addition, rather than being designed to fit into the
language as it existed before the addition. The try statement is
already the most complex statement form in Java; we should avoid
making it even more complex. It would be better to use a new
context-sensitive keyword to define a new construct. For example, one
might select the syntax
identifier (DeclSeq) Statement
Where the identifier would be semantically restricted to "using".
This form is unambiguous with the existing language. I believe it can
be parsed without additional look-ahead by accepting a superset of the
grammar and post-filtering. By selecting a syntax with no overlap to
existing forms, a context-sensitive keyword would not break backward
compatibility. Java 7 will already be using context-sensitive
keyword(s) for the modularity extension, so the approach isn't novel.
(2) Checked exceptions from close() should be discarded for some clients.
Experience with the previous prototype
<http://markmahieu.blogspot.com/2008/01/exception-handling-with-arm-blocks.html>
of the previous (now 3-year-old) ARM proposal
<http://docs.google.com/View?docid=dffxznxr_1nmsqkz&pli=1> raised
issues of handling exceptions from closing resources (some APIs wanted
exceptions from close() discarded, others did not). Others have
raised the same concern this time around. At the time, Josh's
position was "It was certainly my intention that the programmer not be
required to deal with exceptions thrown when terminating a
resource...". That works out very nicely for some clients, and is a
killer for others. The latest proposal abandons the set of use cases
well served by the previous specification, and attempts to serve the
set of use cases where exceptions on termination are best addressed by
the programmer. From my (admittedly limited) experience, I found more
cases where it was appropriate to discard checked exceptions from
close() than otherwise. A revision of the proposal addressing this
was promised. The earlier solution was two distinct but related
statement forms.
(3) The nesting of new behavior with old behavior in the try statement
is a poor fit for many clients.
Another issue arises due to the nesting of new behavior with existing
behavior in the try statement. By retrofitting the construct onto an
existing statement form, the language has made a decision on the
relative nesting of the two behaviors. Is the resource variable in
scope in the catch block? Is it in scope in the finally block? Any
particular answer to these questions is a good match for some clients
and a poor match for others. Given the currently specified nesting,
the following code becomes more awkward when retrofitted with the ARM
construct:
LineNumberReader reader = getInput();
try {
parseInput(reader);
} catch (CharacterCodingException ex) {
report("character encoding error at line " + reader.getLineNumber());
} finally {
try { reader.close(); } catch (IOException ex) {}
}
The proposal should not take a position on a "preferred" way of
combining/nesting resource usage with catch and finally.
This problem is easily solved by separating the new construct from
existing statement forms. Then the programmer can mix or match the
separate language constructs as required for the application.
(4) The proposed construct doesn't retrofit onto many APIs in the
profile of use-cases for which it was designed.
A separate conversation regarding an analysis of the use cases was
moved off-list, ultimately identifying widely used APIs that ought to
be retrofittable with this construct. See
<http://docs.google.com/View?docid=ddv8ts74_1d7tz65fd&pageview=1&hgd=1&hl=en>.
One result of that analysis was that the proposal is incompatible
with a number of widely used types that ought to be retrofitted. A
revision of the proposal addressing this was promised.
(5) Such ergonomic issues require significant time and experience to resolve
A language construct that affects so many distinct APIs with varying
patterns of use requires a significant body of experience to build
confidence that it is usable with them. What little experience we
have with the present proposal suggests more work is needed. In
addition, proposed (but not yet specified) solutions to the
retrofitting issue and promised solutions to other issues are novel
enough that they should not be adopted without a significant period of
time for experience with them to develop, and for the language
construct to evolve based on that experience. We would do a
disservice to Java programmers by shoving an untried solution in a
rush through a process designed to handle small, simple,
noncontroversial changes.
More information about the coin-dev
mailing list