Python nails one - What makes a good language change
rzwitserloot posted in programming on October 28th, 2006
Python 2.5 includes a with syntax (it’s nothing like javascript’s “with”) that helps work with objects that represent resources that need to be explicitly released. Loads of those around, from files to network connections to database sets and queries.
Good.
Basically, you write:
with open("x.txt") as f:
data = f.read()
do something with data
instead of:
f = open("x.txt")
try:
data = f.read()
do something with data
finally:
f.close()
the object returned by open("x.txt") is now ‘guarded’ - it doesn’t matter how the with code block closes, that object gets notified that it should clean it self up.
It’s a good idea. In fact, it’s such a good idea, it’s been floating around java suggestion land for a while now in the form of this proposal by Josh Bloch. Apparently in java7 this has a real shot.
The reason this kind of language change is useful is because it nails 4 important bases for being a good addition to a programming language.
I’ll walk through them, explaining how the ‘with’ stuff passes, and how the world’s dumbest language ‘improvement’ ever, fails it. That would be C#’s coalesce.
1. It fills a needed gap.
Some changes enable stuff that simply wasn’t possible before. Always a tough call to see if the user base starts playing with such a feature. Others are simply a simplification of evolution of things already being done.
with: No brainer - try/finally constructions to ensure proper cleanup of resources amounts to the same thing, and is being used with some frequency today. All those instances can be replaced with thewith syntax. In general, convoluted but often used ‘patterns’ may safely graduate to language syntax. Similar stuff happend with e.g. java’s foreach statement.
coalesce: null checks in series hardly ever happends.
2. It improves readability by higlighting intent.
with: A ‘with’ statement is like a comment. It explicitly shows intent: This variable here is being initialized here with a resource that must be properly released. Not doing so is bound to cause hard to detect memory leaks (go write your little unit tests that find leaks, I dare you!) and a quick hack 2 days before the deadline might easily screw with the delicate balance of a try/finally based cleanup device. With a with powered cleanup guard, you’d have to be real idiot to screw it up now. Excellent.
coalesce: Intent isn’t really an issue here. The long way around, with a load of if statements, or just a library call, is just as obvious in intent.
3. It improves readability by parsing better
with: ‘with’ is somewhat succint for impressing on the reader of the code what’s going on, but it’s certainly not a wild guess like ruby’s puts. More importantly, the shorter code combined with having all the relevant setup/teardown information in one line instead of spread out over just before the try and in the finally block severely helps your brain group things together when glancing at it quickly.
coalesce: I’ve asked over 20 people what ?? would do and not one of them knew. That’s bad. A load of google searches on coalesce amount to saying how cool this little used/overseen C# 2.0 feature is. That would imply those that managed to find this thing didn’t know what it did beforehand. Ruins code readability. Furthermore, Library.coalesce(a, b, c, d, e, f); is not significantly harder to grok at a quick glance compared to a ?? b ?? c ?? d ?? e ?? f, and the method gives you a name to search for and a place for documentation to explain what’s going on.
4. A library expansion doesn’t solve the problem
Oftentimes a language change can be implemented virtually as well using a core runtime library. libraries tend to have the property that documentation for an aspect of it can be found far more quickly compared to a language quirk. Take javadocs/pydocs/etc and compare with finding the exact specifications of the syntax of a python generator. It’s just plain easier to look up some library function. It also avoids having to mess with the compiler, and all related language tools, like all IDEs, templating engines and who knows what. Even if a library solution isn’t quite as elegant as a new language feature, the gap has to be large to warrant tacking more features onto the language itself.
with: with is a language feature but there’s no easy way to pull this off with libraries. In fact, the with statement leads to better ‘librarification’ of your code - the setup and teardown can now be squared away in the object itself, without having to explicitly remember the ’setup’ and ‘teardown’ method calls and putting them just before the try and in the finally. A library hack would have involved passing a pointer to a function together with a pointer to a construction device to some library system, where the library first asks the construction system to produce an object, then call the function in a try/finally guarded block, and call the constructed object’s teardown method in the finally block. Python makes writing on-the-spot methods a bit annoying (if it doesn’t fit into a lambda) and it would have been a serious nuisance to do things this way - it makes the ‘guarded’ code and the stuff around it look more separate than it really is.
coalesce: At least in java coalesce could have been implemented in a better way with a 3-liner library function and generics:
public static <E> E coalesce(E... objects) {
if ( objects == null ) return null;
for ( E object : objects ) if ( object != null ) return object;
return null;
}
That would give you Library.coalesce(a, b, c, d, e, f); instead of a ?? b ?? c ?? d ?? e ?? f. The gap has to be well in favour of the language change to make it a good plan, and in this case I’d actually say the library solution is better.
A couple of other interesting criteria exist but I’d say these are the main ones to consider.

October 28th, 2006 at 15:33
I’d say this is an argument for giving python a real lambda…
As for the ?? operator, I’ll agree that it is ugly and unreadable. Python’s version of the same operator has a much better name, ‘or’.
I guess the GCC version of the operator, ?:, makes a bit of sense since you can read a?:b as shorthand for a?a:b.
Anyway, there is no way to implement it as a library unless your language has cheap lambdas. Your coalesce function evaluates all its arguments first, so it doesn’t work in this case:
return cacheMap[arg] ||= someExpensiveCalculation(arg);
vs
if(cacheMap.containsKey(arg))
return cacheMap.get(arg);
VType v = someExpensiveCalculation(arg);
cacheMap.put(arg, v);
return v;
vs
^cacheMap at: arg ifAbsentPut: [ arg cromulateExtensively ]
October 28th, 2006 at 19:18
“or” does work given truthy/falsy/nully behaviour, but java doesn’t work that way. There are advantages to either, but in the long run I think explicit wins out. As a rule I don’t want my compiler to guess at my intent. Just tell me I need to be more specific, I’ll fix it right then there, takes all of 2 seconds.
as far as lazy evaluation of expressions a la or and and - good point. Hadn’t thought of that. It makes coalesce ever so slightly less of a horrible mistake than it currently is. It just got upgraded from no good use cases to extremely rare good use cases - series longer than 3 null-checks where the operations are expensive or significant.
As far as python’s real lambda: I never got that, either. I mean, I actually like the pythonic whitespace matters syntax far more than the usual brackety C stuff. However, why can’t python be made to accept something like:
[5,4,-3,2,1].sort(def foobar(a, b):
return abs(a)-abs(b)
)
It’s a bit wonky looking but I think it works out to be both readable and a doable job for the parser. Anytime a new line has a shorter whitespace prefix compared to the previous one, close the block. No real difference between the above example and the currently-valid blocks. foobar is a placeholder name. Could be used for recursive functions, otherwise it’s not important.
If that gets confusing, some sort of anondef or def -anon- might help. Either way, anonymous functions are useful things, and having to go through the rigors of giving one a name just to satisfy the parser is probably a bad plan.
Java has a similar predicament - it’s first order functions (anonymous classes) are applicable anywhere but require extreme verbosity. See CICE for a proposal to fix that, but for now, same boat, different hole.
October 30th, 2006 at 6:46
Python’s ‘with’ is exactly what you don’t want to add to your language. If Python had proper lambdas which could involve statements and modify outer variables, your ‘with’ would be a library function. The same goes for Java’s ‘foreach’.
October 30th, 2006 at 12:37
LISP already exists, Slava
October 30th, 2006 at 15:27
A language with proper closures is not automatically a dialect of Lisp.