I got a very interesting response to my last post, about missing features in extended RTTI. Barry Kelly wrote in a comment,
There wasn’t any increase in [RTTI] coverage for XE because other work had priority (64-bit, x-plat, a front end that could support LINQ, more things that I can’t talk about).
None of this actually showed up in XE. The cross-platform work was supposed to, right up until a couple months ago, but they deferred it because it wasn’t ready yet. And the 64-bit work’s been promised but not delivered yet for a long time. (Now they say it’ll be in XE2. I sure hope so!) But… LINQ support in the compiler? Under active development and not a “more things I can’t talk about”? Wow, when did this happen?
Last time I can remember Barry Kelly discussing even the possibility of LINQ in Delphi was two years ago on The Podcast at Delphi.org. A quick Google search doesn’t reveal anything on the subject on his blog since 2008. So I went back and listened to that podcast again. The stuff about LINQ starts at around minute 31, where he leads into it by talking about another new feature he’s currently (at the time) working on: anonymous methods. (And a lot of the hits on his blog when I searched for LINQ were him explaining in comments how support for anonymous methods lead up to being able to use LINQ.)
Some of the interesting points from the podcast:
“The syntax we have for the first iteration of [anonymous methods] in Delphi is a little more heavyweight than the Lambda equivalent in C# 3.0.”
“If we were to try to implement the exact syntax [of LINQ like it is in C#]… that requires a kind of type inference that our compiler isn’t set up to do. … Implementing all that is extra work over and beyond actually implementing closures.”
Here we two concerns about LINQ: The “first iteration of” anonymous methods are too bulky to be used that way, and there’s no type inference in the Delphi compiler. Well, I definitely agree with the first point. As useful as closures are, and I’m not going to say they aren’t, their current “first-iteration” syntax is ugly, bulky, and, frankly, not very Pascal-ish.
We’ve got a rule in Pascal that complex types passed to function parameters match on names, not on appearance. Array parameters are a good example. In another part of that same podcast, Barry mentioned that he doesn’t like the confusion that this rule causes with array parameters, and he’d like to clean it up if he could without breaking backwards compatibility. I’d be just fine with that, but anonymous methods take things to the opposite extreme, to the detriment of the language.
Let’s say I want to sort a generic list with an anonymous method as a comparer. Which is easier to read?
function(const Left, Right: TMyItem): integer
result := Left.ID - Right.ID;
or something like this?
MyList.Sort(TComparison<TMyItem>(result := Left.ID - Right.ID));
The function type name stands in for the function signature, and the parenthesis replace the begin and end, and all that’s left is the actual code. Now, obviously, the second version wouldn’t work if you’re writing a big, long, complex closure like the one found in the LazyLoadAttributes function, deep inside of RTTI.pas. And the first-iteration syntax could still be used for that, but the condensed version is ideal for the sort of simple, “one line filter” style functions that are so common in LINQ queries, and IMO it’s a lot easier to read than C# style lambdas, which are also ugly and not very pascal-ish. So hopefully we’ll end up with a new anonymous method syntax that looks something like what I described above, and the LINQ part of the language could translate the SQL-style query syntax into these anonymous methods behind the scenes, creating the types for the correct function signatures if necessary.
As for the need for type inference… that’s something I’ve never really been able to wrap my head around. It just seems like a solution in search of a problem, and then the problem is ever-so-conveniently provided by the C# language, which, being both a C descendant and a Microsoft product, could be reasonably expected to be full of unnecessary problems.
The example he mentioned in the podcast is a select lambda where you’re selecting a certain property from an object. Something like this:
procedure DoQuery(list: TList<TMyObject>);
var names := from item in list where item.id >= 50 select item.name;
Why does the compiler need to infer any types here? We know what type “item” is because we know what type list is and we know what type comes out of list’s enumerator. (Presumably TMyObject.) The compiler has this information and doesn’t need to spend a bunch of time jumping insanely back and forth throughout the parse tree to “infer” it from anywhere. Once we know what type item is, it’s not hard to know what type item.name is. There are only two places where this code can’t be parsed and typed left-to-right. One is “from item in list”, which doesn’t really present a problem figuring out what type item is if the parser reads the from clause as a single expression.
The other, however, is the completely un-Pascal-ish untyped “var names :=” statement at the beginning of the line. That’s not how we do variable declaration, and I really don’t understand why the RemObjects folks decided to use that syntax in Prism? It looks like it was lifted directly out of C#, which it probably was. But it’s ugly in C# and it’s even uglier in Object Pascal. There are good reasons why we declare variables with their types at the top of the routine, and one of them is to keep from breaking or cluttering up the compiler with stupid unnecessary crap like a type inference parser that has to bounce all over the abstract syntax tree before it can figure out what it’s dealing with.
Am I missing something here? Is there some powerful technique that just won’t work without the type inference? Because I’ve never seen any LINQ example that demonstrates that it’s actually necessary or that it improves code readability at all. And if it’s harder for both the compiler and the coder to read, why bother putting it in?
One of Delphi’s strong points has always been that it gets the details right. C++ is object-oriented programming done all wrong, but Delphi gets the details right and makes writing good code much easier. This seems like a good opportunity to continue the tradition. Don’t clutter up the compiler and the language syntax with ugly, hard to read stuff like inline-declared type-inferred vars and cryptic lambda expressions just because Microsoft (and RemObjects) did it that way. If the Delphi compiler team is working on native LINQ, I’d really love to see them get the details right and give us something that really looks like it belongs in Object Pascal.