Fabulous Adventures In Coding
Eric Lippert is a principal developer on the C# compiler team. Learn more about Eric.
Jeff Atwood was kind enough to once more give me the shout-out in his blog the other day. Thanks Jeff!
This inspires me to continue my series on five-dollar words for programmers. Here’s one that I only learned relatively recently, when I helped write the code that translates a lambda expression into an expression tree which represents the content of the lambda: homoiconic.
A language is said to be homoiconic if the representation of the program can be seen as a data structure expressible in that language. With expression lambdas being convertible to expression trees (which can then be compiled into code at runtime), C# 3.0 is somewhat homoiconic. But it pales in comparison to, say, LISP, where pretty much everything you can do in the language you can also represent as structurally isomophic data.
Something I personally would like to see more of in C# in the future is greater homoiconicity. We could extend expression trees to statement trees, declaration trees, program trees, and so on. This series of steps would enable increasingly powerful and interesting metaprogramming scenarios.
C# suffers from the lack of a metalanguage. We absolutely do not want to go towards the horrible and primitive metaprogramming language exemplified by the C preprocessor language. We already have a great language, C#, so why not use C# as a metalanguage? Wouldn’t it be nice to make C# its own metalanguage? And once we do that, then we get into some truly strange loops, where we could use those data structures in the compiler implementation itself!
This is a long way off and might never happen, but a guy can dream.
> Whenever somebody on the C# team talks about these features, it's like "this is so far into the future" mixed with "in ye olden times, mysterious languages like LISP could do that". Yet, Boo can do all of this, now.
Solving a problem here and now is often not hard. Ensuring that it is solved correctly is harder, and ensuring that it it solved in such a way that it does not create a lot of pain in the future is harder still.
If you've been tracking the design of C#, you know that a lot of seemingly innocuous language features are there for very deep reasons. A good (and relatively well-known) example of that are method overloading rules in presence of inheritance, but I keep stumbling into more and more even years later since I've first wrote a line of C# code, and it never ceases to amaze me. The level of design effort that went into the language is truly outstanding, even despite the occasional blunders (*cough* array covariance *cough*).
This all has its price. You can't just say, "oh I've seen this neat thing in Boo (or Nemerle, or whatever), let's do the same!". Consider that the designers will have to seat down and carefully analyze the requirements and the suggested approach, determining whether it is even the best way to go about the problem (remember, once you do it and release it, you'll have to support it!); looking for potential corner cases and pitfalls (Sam Ng's series on some corner cases of "dynamic" was very illuminating in that respect); finding reasonable solutions to all those; etc. The solution, should any be arrived to, has to be formalized in an unambiguous way for inclusion into the language spec.
This doesn't end here, however. You have to test the feature - that means devising tests for maximum coverage (including all those corner cases!) and implementing them. Then there's IDE - you'd want your code completion, and refactoring, and on-the-fly error highlighting, and debugger support, for all the new stuff, wouldn't you? Finally, documentation - a series of articles on the core feature itself, API reference, a number of how-tos and examples for all of the above...
Now consider the reach and magnitude of the metaprogramming feature, and think about how long each stage of the process outlined above would take. I think it would probably be more involved than LINQ... (and I was really quite surprised by how many new language features were squeezed into Orcas release, considering the time frame).
And, of course, it's not like metaprogramming is the only feature request for future C# versions, and probably not even the most popular one - just go to Connect and search for C# feature suggestions...
If we get that in C# 5, it will be wonderful. But I'm not holding my breath for it. It looks like too big of a fish for now.
Me, I'll consider myself happy if I get "readonly class" and syntactic sugar for CodeContract :)
The macros in Scheme (and Lisp) only work because of the beautifully regular syntax, code is data and data is code. It just wouldn't work in C# or any other Algol derived language. It's amazing to think that over 50 years ago when Lisp was created, it was a near perfect language and hasn't been bettered since.
Here's a radical suggestion but if you want these capabilities to set your creative skills free then why not simply learn Scheme. There is an explosion of interest in Scheme at the moment and it is rapidly edging towards the mainstream. Even Microsoft has woken up to functional languages and has released F#.
The more of us that create libraries for Scheme, the more everyone else can use it for real projects instead of the flawed languages we are forced to earn a living with.
> It just wouldn't work in C# or any other Algol derived language.
As stated above by others, there are counterexamples of that, such as Nemerle (which is pretty close to C# syntactically all in all).
BTW, Microsoft hasn't yet _released_ F# :)
So XSLT is homoiconic and Judy Garland isn't? Go figure.
Hold on there. What's so great about homoiconic? It gives you lots of power -- to tie yourself into knots of un-manageable, un-maintainable, un-readable code. And talk about security risks... HUGE! Ask yourself -- why isn't Lisp used in any major commercial setting? Because making code -> data and data -> code creates huge pitfalls.
I would argue that there are many more reasons than that. A language is successful in a particular domain when it successfully models the processes and concepts in that domain. Business settings tend to have a lot of concerns about processes, so it should not be surprising that procedural coding styles are important. Business settings tend to be concerned about rapidly changing external state, so it should not be surprising that coding styles involving mutable global state are common. Businesses tend to manage human scenarios by throwing the "hierarchy pattern" at problems; it should not be surprising that hierarchical object-oriented programs are common.
Lisp can do all that of course, but the fundamental metaphor that Lisp throws at every problem -- the analysis of lists by functions -- is a far remove from the model world. In C# we deliberately make procedures, mutable properties, and the ability to explicitly model hierarchy all first-class concepts in the language right there for you to use.
Though there certainly are pitfalls with homoiconicity, you can get yourself into even deeper morasses with C macros -- and many people do! The success of C speaks to the desirability of giving programmers "enough rope". You can't go denying people a tool just because some will misuse it horribly. But of course we want to make sure that the tools are designed to be easily used well, and designed specifically for real-world scenarios. -- Eric
And any coneptual advantages of of doing so pail in comparison to the problems caused. Algol and Algol-derived languages have maintained that separation for very good reasons. Just because something can be done, and because there may some academic symmetries in such a concept, doesn't mean it should be adopted. And just because programmers love to produce "cute" (if obtuse) code doesn't mean we should design commercial languages to cater to that anti-pattern. Careful language design needs to take into consideration much broader concepts of usefulness and useability. I think the C# team has done a pretty good job of maintaining that perspective.
Indeed. We're not interested in homoiconicity and metaprogramming because its cool (though it is.) We're interested in it because we have many customers whose fundamental use of C# involves in large part the automatic generation of more C#. Our existing code models are not strong enough to do all the tasks our customers would like them to do. -- Eric
Ahora que ya hemos visto que es una expresión lambda podemos ver otra nueva característica mucho más
But, Eric, Common Lisp has a well fleshed-out OO system, and it not really FP-centric - at least most CL code that I've seen uses mutable state and imperative constructs (such as loops) heavily.