SKerwin: May 2007 Archives

Thompson For President

|

The Inevitable Lispness of Coding

|

I didn't want to believe it, but it's slowly becoming inescapable: all roads lead to Lisp.

Some months back I ended up writing some rather complicated C# code; it had to dynamically assemble the appropriate JavaScript code to handle the client-side navigation in the new interface to an older product. I'm not going into any specifics because frankly I don't remember them, but there came a point in my coding when I began to feel a profound sense of déja vu -- after a moment of introspection I realized the code I was writing differed from a block I hard written the previous day in all but the innermost three lines of an enormously complicated multi-method deeply-nested looping mess. My memory's hazy, but I think there was recursion involved, and there may have been snakes.

Anyway, I did what any lazy C# coder would do; I abstracted the outer logic into a single method that took a C# delegate as a parameter, and called it with two different delegates to handle the two different inner sections of this monstrosity. For the uninitiated, by the way, a C# delegate is an enormously complex and advanced computer science construct that most of the smarter folks in the field call a 'function'. Why they felt it was more efficient to add this new thing instead of just adding first-class functions -- and believe me that their approach is the hard way, because delegates by their design basically require you to think about things like closures and continuations very carefully -- continues to elude me, but if the only screwdriver available is a left-handed electric drill powered by a gerbil in a wheel I know where I'm going if I've got a screw. The main point of this paragraph is that I was conscious of the ugliness of my solution as I was writing it; it was just structurally inelegant. The fact that the C# 2.0 anonymous delegate support (mmm, syntactical sugar) negated the need to define independent functions didn't really help; it was ugly. It would've been much better to simply pass some code into the function directly, somehow.

Today I had to revise a caching system; items can be fetched based on one of two criteria, and storing two copies in the cache was ugly. My solution was to cache them under a single identifier and maintain a map from the first type of identifier to the second. It's not a terribly complex thing, but the vagaries of the .NET caching system and some additional constraints imposed by the app made it relatively difficult to get exactly right. When I realized that I needed to apply this same methodology to several different kinds of things, I sighed deeply and got to work writing several delegate definitions and adding <generic> tags all over. In the end I had a system that could be used for any class fetchable by these two criteria (I didn't feel up to building it generic enough to multicache based on any two criteria). Two delegates, two wrapper methods, etc. Would've been much easier to just pass a block of code in, but you do what you can with what you have where you are, n'est-ce pas?

It turns out that Microsoft has been having the same sort of problem, actually. Far too many people have written off the new features in C# 3.0 as mere syntactical sugar to make LINQ easy, but there's at least one item of massive importance that a lot of people seem to have missed. The new lambda expression in C# 3.0 are more than just a still-terser form of anonymous delegates, because in some situations -- and I'm still not clear as to the when or the why, nor about whether non-Microsoft coders can easily join in the fun -- they aren't sent as delegates, but as parsed expression trees. Trees which the receiving code may then, if it wishes, alter, compose, or mangle in whatever way it sees fit.

That's how LINQ-to-DB works; the syntactical sugar decodes into a bunch of lambda functions which are passed as expressions to the appropriate LINQ-provided method, which can then interpret, parse, and combine them so as to produce a reasonable query (as opposed to the query-all-join-in-code that would be necessary without this change). Suddenly, like a bolt from the blue, .NET supports passing code to a method.

It remains to be seen whether anything useful can actually come of this, of course. If it's only used for LINQ and nothing else then it's just another example of Microsoft embracing and extending, though I guess it's a bit less nefarious when they do it to their own product. But if library-builders can wrap their brains around this -- and if the implementation is robust enough to be useful for anything beyond the narrow case for which it was invented -- this is pretty cool stuff.

Here's hoping .NET 4.0 lets an assembly access itself as an expression tree, eh?

In C++, I could write a template like the following:


std::map<string, string> PropertyList;

template<class T>
T GetProperty(const string & propertyName) {
return T.Parse(PropertyList[propertyName]);
}

And it would work so long as it was used properly; if you tried to fetch a property of a type that lacked a Parse method, you'd fail to compile. The error message would be a hideous mess, but as long as you the programmer made sure that all your Ts could .Parse, you were golden.

Compare and contrast with C# (2.0):


Dictionary<String, String> PropertyList;

T GetProperty<T>(String propertyName)
{
return T.Parse(PropertyList[propertyName]);
}

That won't work, because the .NET compiler wants to be sure that every potential T that this generic method could ever meet has a .Parse method. Really sure. This appears to be because the .NET 2.0 runtime contains intrinsic support for generics, which is actually a really neat feature; in C++ each instantiation of a template becomes its own chunk of code, and the space requirements grow far faster than most programmers expect. In this sense at least C# generics are far superior, as they propagate the savings in time and effort from the programmer all the way through to the execution environment, while C++ saves the programmer some time but makes the end user pay full freight.

So what's Microsoft's solution to the problem? Pretty much what you'd expect from a company that likes solving every problem with a database: they added a 'where' clause.


T GetProperty<T>(String propertyName) where T : IParseable
{
return T.Parse(PropertyList[propertyName]);
}

Oh, neat! That earns me two things: first, the IDE will stop me if I try to invoke this for a type that doesn't implement the IParseable interface and will do so with a polite error message; second, I can now call any method defined by the IParseable interface within my generic method.

Problem solved, right? Nope! Because there isn't an IParseable. Ain't no such beasty. And if I need to support arbitrary built-in types for my property-fetching system (and it turns out that in the situation that inspired this missive, I do) I'm pretty-much S-O-L. Even if I could jump forward in time to get a release copy of the new Orcas build of Visual Studio and upgrade to C# 3.0 with those shiny new extension-methods (which, by the way, Objective-C has had for ages under the name 'Categories' -- but that's another rant) I'd still be screwed. Extension methods would allow me to put a .Parse method on any and every class I want, but they don't provide me a mechanism to inject interfaces into existing classes. The best I could do is inject .Parse into the object base class and then override it appropriately for each case I care about. Of course that depends on the fact that built-in methods with the same signature as extension methods override the extension, and from what I've read that particular behavior's still a little bit up in the air.

It also leaves me in a situation that's worse than C++ started from, at least from a maintenance standpoint; I can now attempt to query any object of any type using my GetProperty method, and it'll compile just fine. But if it's a type for which my generic object.Parse injection won't work, I'll find out at run time instead of compile time. Really the best thing I could do would be to make my default object.Parse method throw a NotImplementedException with a helpful error message, and I think anyone who's worked on a large project will agree that that kind of solution is a special new kind of suck.

So what's the solution? Well, it's to cheat:


T GetProperty<T>(String propertyName) where T : IParseable
{
(T)typeof(T).GetMethod("Parse", new Type[] { typeof(string) }).Invoke(null, new object[] { PropertyList[propertyName] });
}

I feel dirty writing that, but at least it compiles and works -- as long as you remember not to pass in something without a Parse method, because that's a one-way-trip to UnhandledExceptionville.

Pages