In the MvcContrib project there is an HTML helper for putting data into a <table>. The syntax looks like this:
<%= Html.Grid(item.Addresses) .Columns(column => { column.For(a => a.City); column.For(a => a.Country); column.For(a => a.Street); }) .Attributes(style => "width:100%") .Empty("No mailing address present!") %>
I remember thinking this API was rather cool when Jeremy Skinner presented the syntax on his blog. The syntax became the topic of discussion in a recent Stack Overflow post - Abuse of C# lambda expressions or Syntax brilliance?
I'm fascinated, yet at the same time repulsed, by a syntactic trick used in the Grid syntax:
.Attributes(style => "width:100%")
The syntactic trick is how the API uses the name of the lambda’s parameter as the name of the HTML attribute to set. You can set a bunch of the table attributes like this…
.Attributes(style => "width:100%", id => "grid", cellpadding => "0")
…and it will render …
<table id="grid" style="width:100%" cellpadding="0"> .... </table>
Beautiful, don’t you think?
I can appreciate the following thought process:
I mean - nobody expects the name they pick for a parameter at compile time to be so important during runtime. The Stack Overflow post has a couple choice comments, including two from Eric Lippert:
This is horrid in so many ways
And …
I just asked Anders (and the rest of the design team) what they thought. Let's just say the results would not be printable in a family-friendly newspaper
I don’t think one of the goals of C# is to “be like Lisp”, but I like the code, and it reminds me of a 1993 essay by Paul Graham titled “Programming Bottom-Up”.
Experienced Lisp programmers divide up their programs differently. As well as top-down design, they follow a principle which could be called bottom-up design-- changing the language to suit the problem. In Lisp, you don't just write your program down toward the language, you also build the language up toward your program. As you're writing a program you may think "I wish Lisp had such-and-such an operator." So you go and write it.
The project I’ve worked on for the last part of the year was a huge technical challenge. I bent the hell out of C# to try and make something that was easy to read and maintain. Looking back now, I can see a lot of mistakes and plenty of room for improvement, but I also believe if the project didn’t use some tricks with extension methods and Expression<T>, we’d be facing 3x the amount of code and 3x the complexity.
Since that time I’ve become a big believer in bending languages. You have to look past the tricky implementation details and see the bigger problem being solved.
Comments
Code that's hard to write now is always 10 times harder to maintain later.
My argument for why this is good seems pretty simple:
1) The code that does this isn't *that* hard to understand
2) All code that uses it becomes much more readable
3) The total of all code in #2 is going to be modified more often by several orders of magnitude than the code in #1
I added support for this to the MvcContrib grid because I really dislike the use of anonymous types as dictionaries. I felt that this produced a cleaner result, but it's definitely language abuse :)
One thing to point out is that this is completely optional. All methods that take a 'lambda hash' also take an IDictionary as a separate overload.
@Duncan Mak,
This approach really isn't very expensive at all (see blog.bittercoder.com) and if you don't like it, you can always use a Dictionary + collection initializer. The idea wasn't to avoid quotes but to just make something that's a bit shorter to type.
Jeremy
It's a series of methods in a class that is meant to be subclassed to make a data access layer for a project. You add methods to this subclass that exactly match the names of the stored procedures in your database. The code then walks the stack trace looking for your method (tagged by an attribute) and uses the names of the method and its parameters to construct the SqlCommand object needed to execute the stored procedure. It does some more shenanigans to process the results, but maybe you get the idea. Like I said, if you're really interested to see the tomfoolery, you can find it on CodePlex.
LINQ and lambdas are hugely important for it, the C# 2.0 version (necessary for some projects here at work) is easily twice as long.
I love the Paul Graham quote, and while LISP'ers can do basically anything they want with their language, we can still raise the bar in C# with things like this.
My take: Beautiful; not a hack.
<%= link_to 'Link Text', @model_object, :style => 'width: 100%', :id => 'model_link' %>
which would render as
Link Text
I think it is an elegant syntax, my only problem is that it is not exactly a conventional way of doing things in the C# world, to the point where it would not even be usable unless the person read the documentation before using it.
I think it is a beautiful way to push the language into interesting places. What I am saying is that it completely violates the principal of least astonishment (en.wikipedia.org/...), which is something you should strive for in public code.
I recall taking it a bit further with nesting as per this post
Personally I feel these kinds of hacks are an indicator of a missing language feature, rather then something elegant (much like design patterns can be).
In this case I would much rather see language level support for symbols / more succinct dictionary initializer syntax. Or even better, support for AST macros / compiler pipeline extensions, like the often used "@symbol" => string transformation you see in many Boo-based DSL's.
When I first saw that in the MVC examples I thought:
It's a nice hack but I can't believe they are actually going to use this as the recommended way to pass parameters...
"I mean - nobody expects the name they pick for a parameter at compile time to be so important during runtime."
This is a fundamental part of Rails development, and part of the reason it's so quick to write Rails apps.
This does seem to go against the purpose of lambdas and adds unnecessary complexity.
Also - I saw that "bend" building in tokyo, and took my own picture! haha nice coincidence, especially as it was sort of hidden on a side street!
picasaweb.google.com/.../Japan02
This is effectively the same goal of DSL's.
As long as it remains intuitive to read then I have no problems with API's that 'bends the language'.
Personally, I prefer to look at html when in the view, not something designed to hide it.
If I wanted to hide my html I would have stuck with web forms.
"Why use all that expensive reflection machinery..."
I just wanted to point out that this probably doesn't use reflection at all. In C# when you write a lambda expression it will be compiled into 1 of 2 things, either it will be compiled into an delegate if the parameter type it is used as is a delegate (for example Func<T>) or it will be compiled as an expression, or Expression<Func<T>>. In the case of the expression, you can inspect the lambda, including parameters, without requiring reflection in the .NET sense (as in the System.Reflection API). I've seen the right hand side used this way before and it's pretty useful for things but not the left hand side.
So for people who are concerned about performance due to reflection costs can be relieved a little bit. Not that its free to look up the parameter name but it's extremely trivial (and cache-able if you're really worried).
The basic rule of thumb is that code is twice as hard to maintain as it is to write. If you're writing code you consider clever you are, by definition, not qualified to maintain it.
That being said, I also don't see all language bending to fall into that category. This particular piece of code is elegant and simple to understand (and I don't think lambas are easy to understand, lol)
Toss a couple of comments in there as to why you did it so that you'll never forget and run with it. :)
attributes do |style| "width:100%"
I’ve been trying to specify the “class” attribute of the , but I can’t use the hash syntax:
.Attributes(class => “myCssClass”)
because “class” is a C# reserved word, is there any way to do this? (without having to create an IDictionary)
Thanks!