Re: Why we should (not?) have closures after all
- From: zerg <zerg@xxxxxxxx>
- Date: Wed, 22 Oct 2008 12:47:07 -0400
John B. Matthews wrote:
I'm not sure that's a fair characterization. Both sides want to produce correct, reliable software. Programmers have normal human variability. Programmers should have tools that can tell them as much as possible about their code's likely behavior.
And, of course, static type checking is one of those tools.
It used to be that the two most common and nasty to track down stack-trace-producing bugs in Java programming were NullPointerException and ClassCastException, at least in my experience.
Since Java 5 and its generics came along, that's changed (again, in my experience) to just NullPointerException.
So, if you ask me it's high time we had some compile-time null checking. A new type wart that means "cannot be null" might be nice, given the following semantics:
* Declarations without the wart, including all in existing Java code,
allow null values.
* The wart can be used anywhere a type is used in declaring a reference
type variable, including an argument, and can also be used on method
return types and generics type parameters. It cannot be used, however,
in "extends" or "implements" or class declarations in general, other
than in generics type parameters, so you cannot create a class all
references to which can never be null.
* The compiler does nothing special if a "can be null" reference is
assigned to a "can be null" reference (including returning it from a
method whose return type "can be null").
* The compiler does nothing special if a "cannot be null" reference is
assigned to any reference (including returning it from a method).
* When a value that is known at compile time is assigned to a reference,
the compiler does nothing special unless it is known to be null. If it
is known to be null (e.g. the null literal) and is assigned to a
"cannot be null" reference, this is an error.
* When a "can be null" reference is assigned to a "cannot be null"
reference, returned from a "return value cannot be null" method, or
similarly (including passing it as an argument to a method whose
corresponding parameter "cannot be null", and including where the
reference is being passed to something generic and the type parameter
restricts it from being null), the compiler generates a run-time
check that throws a NullPointerException if a null reference shows
up.
Optionally, an explicit cast might be required to assign from "can be null" to "cannot be null", with this cast generating the check.
Why? Because nulls where they don't belong are really type errors, and the compiler should do more about catching type errors. Also, because "cannot be null" is type information that a programmer should be able to embed in variable declarations, method arguments, and method returns.
Even the weaker version (no explicit casts required) helps catch more bugs at compile time (as the programmer thinks about what should be marked "cannot be null", or looks at API docs and notes that these parameters may be null, this method may return null, etc.; plus the code is a bit more self-documenting).
It also will catch many bugs earlier at run-time and closer to the real programming error, as NPEs get thrown "eagerly" when a null first gets into someplace it shouldn't, instead of "lazily" when some code eventually actually tries to invoke a method on that null, and the trail has long since gone cold regarding how that null snuck in there in the first place.
(That is a similar benefit to generics causing type errors in collection code to be caught at compile time or when objects are put into the collection, instead of when objects are pulled out that might have been put in there hours ago and far away in space and time, especially if the collection has been serialized and deserialized in the meantime. And indeed one big benefit of the above proposal would be that one could declare generic collections that throw NPE if an attempt is made to put a null into the collection, instead of the null lurking in there like a time bomb and only blowing your program up later when other code tries to use the contents of the collection.)
This would also help with method guard-code clutter. A lot of defensively-written methods will start with lots of "if foo throw this, if bar throw that", mainly IllegalArgumentException and/or NPE, and sometimes IllegalStateException.
In fact, it would be nice if there was a variant of the "assert" statement that was always enabled and threw IllegalArgumentException instead of AssertionError. I suggest overloading the "final" keyword, since it sort of makes sense, and having code like this:
public int FooMethod (int x, Foo* y) {
assert final x >= 0 && x <= 10;
// Do stuff with x and y
return something;
}
equivalent to the current:
public int FooMethod (int x, Foo y) {
if (x < 0 || x > 10) {
throw new IllegalArgumentException("x out of range");
}
if (y == null) {
throw new NullPointerException("y");
}
// Do stuff with x and y
return something;
}
Much less clutter and conveys the same information, plus it's possible for the compiler to make some compile-time checks -- even for x, it might notice for instance if FooMethod is ever called with a literal or otherwise compile-time-constant first argument that is out of range (since, in this instance, the boolean expression doing the range check contains only "x" and compile-time constant expressions; obviously, in the case that the assert checked x against an instance field, say, the compiler could not generally do such a check; though it could still flag e.g. "FooMethod(-1, y)" if the check were "if (x < 0 || x > this.maxX)").
In a nutshell: "final" after "assert" and before the boolean would make the assert always active in production code and would make it throw an IllegalArgumentException. (Perhaps IllegalStateException if the boolean expression only involves instance state, and UnsupportedOperationException if it's "assert final false;".)
And for a third wishlist item: a way to specify that a method does not return (i.e. always throws an exception or exits the VM). I'd suggest using "null" rather than "void" as the return type token, since this makes some amount of sense and is otherwise unused. It would be an error for a "null" method to actually return (so every execution path has to hit a null method or a throw, including any path starting from a catch). System.exit would be declared "null" in the standard API.
This would be useful when you have code like this:
public Foobar parseFoobar (File fooFile) throws IOException, ParseException {
...
if (whosit.isScrewedUp()) {
throwPE(fooFile, line, "whosit is fubar'd");
}
...
if (something.isOK()) {
return new Foobar(something, somethingElse);
}
throwPE(fooFile, line, "something is fubar'd");
}
private void throwPE (File file, int line, String message)
throws ParseException {
throw new ParseException(file.getName() + ':' + line +
": " + message;
}
Currently, the compiler will complain that parseFoobar doesn't seem to always return a value, because it doesn't know that that last throwPE never returns normally (even though it's in the same file and "private"!); declaring it "null" would allow the compiler to accept parseFoobar (and to check that throwPE does observe the programmer's intention that it never return normally, too).
Right now, the alternatives aren't pretty:
* Rearrange the parseFoobar method, e.g. to end with
if (!something.isOK()) {
throwPE(fooFile, line, "something is fubar'd");
}
return new Foobar(something, somethingElse);
This isn't bad, but in some cases might require much more contortion
and result in deeper nesting.
* Add a "throw new Error(); // Can't happen" at the end of that method.
(Ugly too, but works.)
* Get rid of throwPE and have all of the string-concatenation and other
boilerplate repeated endlessly in parsing methods, in violation of
the guideline of "specify everything exactly once". In this case,
"how to structure parse error messages" would no longer be specified
(and changeable) in only one place in the code.
The goal is to find as many problems as possible, as early in the process
as possible.
Exactly.
.
- References:
- Why we should have closures after all
- From: Lew
- Re: Why we should (not?) have closures after all
- From: Andreas Leitgeb
- Re: Why we should (not?) have closures after all
- From: Robert Klemme
- Re: Why we should (not?) have closures after all
- From: Andreas Leitgeb
- Re: Why we should (not?) have closures after all
- From: Hendrik Maryns
- Re: Why we should (not?) have closures after all
- From: Andreas Leitgeb
- Re: Why we should (not?) have closures after all
- From: Robert Klemme
- Re: Why we should (not?) have closures after all
- From: Andreas Leitgeb
- Re: Why we should (not?) have closures after all
- From: Patrick May
- Re: Why we should (not?) have closures after all
- From: Joshua Cranmer
- Re: Why we should (not?) have closures after all
- From: Patrick May
- Re: Why we should (not?) have closures after all
- From: Mark Space
- Re: Why we should (not?) have closures after all
- From: Patrick May
- Re: Why we should (not?) have closures after all
- From: Mark Space
- Re: Why we should (not?) have closures after all
- From: Patrick May
- Re: Why we should (not?) have closures after all
- From: Mark Space
- Re: Why we should (not?) have closures after all
- From: Patrick May
- Re: Why we should (not?) have closures after all
- From: John B. Matthews
- Why we should have closures after all
- Prev by Date: Struts - accessing the ActionForm bean in JSP
- Next by Date: Re: Icon to ImageIcon or Image
- Previous by thread: Re: Why we should (not?) have closures after all
- Next by thread: Re: Why we should (not?) have closures after all
- Index(es):
Relevant Pages
|
Loading