Seems like a cheap way to get upvotes.
File.open("...") do |f|
firstline = f.readline
... stuff that might throw an exception ...
end
If the "stuff" throws an exception, the file gets closed automatically. And while File is a library class, it's getting no special favors here --- any pure ruby library can easily implement similar APIs, and ActiveRecord's connection pool, for example, actually does. using(var resource = new Resource())
{
// potential code that throws exception
}http://download.java.net/jdk7/docs/technotes/guides/language...
with open("some.filename") as f:
...
f is an opened file which is automatically closed. This isn't strictly necessary in Python, since files are flushed and closed when reaped, including in case of exception, but it's useful. You can also do this with all of the threading primitives: with threading.Lock():
do_that_one_contentious_thing()
Useless syntactic sugar? Maybe. It's an explicit scope which makes certain guarantees, though, so it's not just fluff.What was his other example? DB connections? Well, in Python, the DB API requires that DB connections not easily leak, but if you're stuck with a crappy driver, you can still auto-close your connections:
with contextlib.closing(dbapi.Connection(...)) as handle:
cursor = handle.cursor()
...
So it's definitely possible.I think it's unfair of him to pick on Ruby and Python just because their syntax is more oriented towards assuming the garbage collector is non-sucky and exceptions aren't expensive.
Edit: Fixed formatting.
RAII will be in the next great language, as it is a useful tool. This is not an academic concern, it is something that happens all the time due to rushed deadlines, stressed developers, or simple naivete.
People love to slag off C++ but the higher-level devs have done some serious thinking about how to engineer robust programs. Sutter's 'Exceptional C++' is eye-opening the first time around, and the concepts are applicable to any language that has exception handling. Programming in a transactional manner has visibly improved my designs -- mostly through the paranoia that almost any statement could throw an exception.
Also there's no mention about the fact that Java forces you to check for exceptions (well not for unchecked ones, but those should not be fatal, and I never really got their idea). You can't forget the try-catch.
try {
mightFail();
} finally {
doCleanup();
}
instead of this: mightFail();
doCleanup(); class C {
void mightFail() { ... }
~C() { doCleanup(); }
};
C c;
c.mightFail();
Anyway, I hate this slide deck so much.1. The author's point could be made in far fewer slides. As in, like, two slides. I hate presentations that are disrespectful of the audience's time for the sake of being cute.
2. I am generally unimpressed by arguments of the form: (a) Language X has flaw Y. (b) Therefore language X is unsuitable for development. This can be instantiated for every language X for some flaw Y and is not an argument against any language. You need to additionally make the argument: (c) Y is so serious that it outweighs the considerations in favor of X and against the other languages one might reasonably use. Of course (c) is an incredibly high bar, which is why most anti-language zealot arguments do not even attempt to make it, and also why most anti-language zealotry is silly.
In order to do (c) in this case, you would have to make the case that writing finally clauses is worse overall than, e.g. debugging memory corruption errors, and writing copy constructors and overloaded assignment operators, and all the other baggage of C++, rather than handwaving them away with "the downsides have been exaggerated".
Which, by the way, is ironically the biggest flaw of this slide deck: the author vastly exaggerates the downsides of finally. try/finally is no worse than checking the result codes of C syscalls, and it is sometimes better. I don't recall the C style of programming stopping Ken Thompson from building great things and I doubt that try/finally is actually what stops Java programmers from building great things.
There is no argument against try/catch/finally, the argument is that the most common usage of try blocks is for dealing with resource management, not actual exceptions in program state. Given exceptions should be used to expression 'exceptional' program states, that is a significant downside to code readability.
Syntactic sugar like using/yield/with blocks improve the signal to noise ratio of try block usage but still rely on programmer acceptance of that idiom. Ideally, the responsibility of cleanup would be moved entirely to the class implementation rather than the consumer. C++ did this with destructors. In a managed world where maybe you don't always want an eager dispose, rather than syntactic sugar in the caller, move it to the signature of the dispose. Something along the lines of
public scoped void Dispose() { .... }
Alternatively, @Scoped or <Scoped> if you don't want more keywords. The topic is partly to blame but RAII is orthogonal to whether a language is garbage collected or not. It's sad that in an age where PLs are undergoing a sort of renaissance period, mention of C++ causes everyone to circle their respective wagons.
All in all, he's arguing for RAII, which is impossible in Java and a bunch of other popular languages.
I cannot remember where I saw this (which is a giant problem in itself because I can't remember the details, just that there was a gotcha...) but I read someplace that it is actually pretty easy to introduce disastrous bugs into try/finally blocks. Perhaps it had something to do with managing locks. It could have been the Go guys who said it when talking about why Go doesn't support exceptions, or perhaps it was in multicore literature (perhaps TBB talking about its RAII locking mechanism?). If anybody has any ideas what it is I'm trying to remember here, please comment. If not, well, ignore.
That's kind of the point. The variable will only be assigned a value if mightFail returns normally. The scoping rules make it impossible to accidentally use an uninitialized variable (i.e. a variable whose first assignment was not reached due to an exception).
Although if doCleanup() also does some additional cleanup, like cleaning up some associated resources that aren't scoped to this block then I think you're still screwed. Maybe someone can clarify if I'm wrong.
:)
DB.open(new Runnable() {
public void run() {
// ... do stuff here ...
}
});
You can even design your resource layers such that they can only be used this way (or are easiest to use this way).Basically, I'm just stealing the JavaScript-y way of doing this that uses closures:
DB.open(function() {
...
});Blech. This might work for some cases, but would be a huge pain in the ass in others.
But the fundamental difference is that if you have code that does
a()
b()
c()
then you can guarantee that a() will be executed, then b(), then c(). And if there are any branches in case of errors, they will be explicit. Exceptions surround every statement with the possibility of an unannounced exit.http://blogs.msdn.com/b/oldnewthing/archive/2005/01/14/35294...
In Go, it's even easier to recognize ignored error codes because you tend to have fewer levels of indentation and sometimes an explicit "_" when you're discarding a return value.
The same for Java. I honor all the exceptions in the standard/platform libraries. But I see past the hype for my own interfaces. Imho, 9/10 exception classes clutter the interface. 9/10 (again for high reliability, high quality code that you want to work reliably but also be flexible enough to extend), what you want to return is a boolean (and log) for stateful methods. Meanwhile prefer stateless methods wherever possible. And generally speaking treating the JVM and Java as basically a really really high performant scripting engine (i.e., closer to JS than C; though the syntax is somewhere in between). Imho, if you can't do RAII, and you're not deterministic, you are basically a scripting language (or are in the GC family of languages, if you don't like the term 'scripting' -- I think it's cool...).
Anyway, that's how I approach it. But I don't buy into the hype of exceptions most of the time (though of course I honor whatever contract other libraries use).
Thats why checked exceptions are such a bad idea... Personally, I really like Go's defer, panic, recover mechanism[1] for handling exceptional circumstances.
[1] http://blog.golang.org/2010/08/defer-panic-and-recover.html
void myMethod()
{
disposable File myFile = new File(somePath);
// ... do stuff with the file
// "disposable" modifier causes myFile to be
// forcibly destroyed upon leaving scope for any
// reason (except if the disposable object itself
// is returned from the method).
}
An idiom designed specifically for the purpose of resource management would make for a far cleaner implementation than shoehorning an existing mechanism.You'd probably also need to add checking for references to the object by still-living objects (i.e. objects not eligible for gc). If any live object has a reference to the disposable object, its "disposable" status gets removed. Similarly, returning the disposable object from the method also strips its "disposable" status. It would add extra processing at the end of the scope level, but generally methods that create/use resources don't need to be lightning fast anyway.
You could even add the "disposable" modifier to class definitions, making all instances of that class disposable by default (and thus destroyed unless referenced or returned).
static File globalFile;
void register_file(File aFile)
{
globalFile = aFile;
}
void myMethod()
{
disposable File myFile = new File(somePath);
register_file(myFile);
}
If you answer, "add referencing counting", reference counting isn't perfect because you can create cyclic references.The only reason RAII works in C++ is because you can refer to an object by value and separately by reference. You can create stack-based objects that have a defined scope.
You really can't have this in a language that always treats objects only by reference.
Reference counted systems only work if you don't create cyclic (strong) references, so that argument is moot. In fact, reference counted systems can deal with resource objects easily so long as the compiler/interpreter ensures that pending autoreleases are executed when unwinding the stack during an exception.
X x = null;
Y y = null;
try{
x = foo();
y = bar();
yadda(x,y);
} finally {
if (x!=null) x.dispose();
if (y!=null) y.dispose();
}if (x != null) try { x.dipose(); } catch (Exception){} }
it's why the using keyword is so nice. Still, this is all hoops languages force us to deal with when they shouldn't (which is the OPs point)
Util.dispose(x,y);
...and let that handle all the possible issues.public void withConnection(Callback<Connection> callback) { Connection connection = createConnection(); try { callback.call(connection); } finally { connection.dispose(); } }
Once Java actually gets closures it'll make this soooo much nicer.
While I agree that Java sucks because it makes certain very common things require extreme verbosity, worrying about garbage collection isn't all that important except in systems-level programming (which isn't done in Java really), and large GUI that need tons of memory and still need responsiveness. But many people wouldn't even think to use Java in those cases anyways, so I'm not really sure what this guy's point is.
http://docs.google.com/viewer?url=https%3A%2F%2Fs3.amazonaws...
Edit: Warning. Actually seems to cut off some of the slides.