Logo




Subscribe:
RSS 2.0 | Atom 1.0
Categories:

Sign In


[Giagnocavo]Michael::Write()

 Wednesday, August 27, 2008
ASP.NET MVC Begs for Tuples

[Yea, I’m not a web dev, and actively avoid it as much as possible, so I’m late to this party.]

The interesting thing about ASP.NET MVC is that it takes an opposite approach to ASP.NET in general. ASP.NET concepts try to “build up”, so as to shelter us from the evolved idiocy that is HTML, as well as clean up the inherently stateless nature of HTTP. ASP.NET MVC makes no attempt and forces you to deal with reality. Considering ASP.NET’s abstraction isn’t really perfect (example: databinding sucks), MVC’s approach is unfortunately refreshing.

Because of its “raw” nature, you’ll be writing a lot more HTML than you’d do with ASP.NET, and this HTML must line up with code on your server. To make this less of a pain, there are some HTML helper functions. Rob “ type inference” Conery has an overview here.

Here’s the signature for one of the functions:

    public static string CheckBox(this HtmlHelper helper, string htmlName, string text, string value, bool isChecked, object htmlAttributes);

The last parameter confused me. Why would it be an object? Am I supposed to pass in an IDictionary<string,string>? Just a long string? To make it more confusing, other helpers had two overloads:

    public static string TextArea(this HtmlHelper helper, string htmlName, object value, IDictionary<string, object> htmlAttributes);

    public static string TextArea(this HtmlHelper helper, string htmlName, object value, object htmlAttributes);

OK, so they explicitly called out the IDictionary there – THEN WHO WAS OBJECT HTMLATTRIBUTES?

Rob covers in his overview. The idea is that you’re supposed to use anonymous types to hack around the lack of tuples.

<%=Html.Whatever(arg1, bla, …,
                             new { @class=”cssx”, style=”x:f” …}) %>

What a great case of not-having-built-in-tuples-is-lame. It’s so lame, Microsoft’s own developer teams have to resort to weird (but quite creative!) hacks like this so that their syntax won’t completely suck*. Damn.

And now, a duck

For bonus points, there is another C# compiler feature that the MVC team could have [ab]used, and it would arguably have made more sense (although the syntax isn’t as tight). C# supports duck typing on collection initializers! So, they could create a class like this:

    public class HtmlAttributes : System.Collections.IEnumerable

    {

        public System.Collections.IEnumerator GetEnumerator() { ... }

        public void Add(string name, object val) { ... }

    }

And then they can write this:

new HtmlAttributes { { "A", 123} , {"B", "test"} }

No, it’s not as tight as the anonymous type syntax, but it does make a lot more sense.  And tuples still make much more sense than either approach, and have benefits for the rest of the language to boot (death to out parameters!).

 *The only benefit I see in anonymous types is that, at compile time, you'll  know there are no key conflicts - but that is totally trivial in the way they use them, since all the keys are declared right there.

ASP.NET | Code
Wednesday, August 27, 2008 1:12:13 AM UTC  #    Comments [10]  |  Trackback

Wednesday, August 27, 2008 11:31:24 AM UTC
we get it, you wish C# was purely functional. just use F# instead for everything non-web dev
Wednesday, August 27, 2008 12:37:59 PM UTC
There's no doubt that C# isn't really equipped for MVC and that Ruby (which is used in Rails, which again is the template ASP.NET MVC is built upon) is a much better fit for the dynamism required in an agile web framework like this.

I think ASP.NET MVC goes a long way to remedy the horrible monster of Frankenstein ASP.NET WebForms are, though. It's far from the beauty that is Ruby on Rails (especially considering Microsoft's alternatives to ActiveRecord and such), but it's getting there. With new revisions of C# getting more dynamic and MVC hopefully being the architecture of choice to build ASP.NET applications in the future, there's at least great hope that it will become just as beautiful.

Time will tell. I'm not looking back, at least. WebForms must die. :-)
Wednesday, August 27, 2008 8:36:10 PM UTC
@Vijay

No, I don't wish C# was purely functional. Nor is F# purely functional. Mutation comes in quite handy. Hah, even right now I have that exact problem at work. We have a large (2GB+) "readonly" object tree in memory. Changing a single leaf node would usually mean copying the entire thing over again. Instead, we use a bit of mutation to mutate the certain references we need changed, while keeping most of the objects the same. I'm not sure how I'd do that in a purely functional language.

At any rate, I don't see why tuple support implies functional. Creative use of types as a hashtable _in C#_ seems rather wierd and tuples represent what they're doing more accurately, IMHO. (Not to mention tuples are so handy in tons of other areas, and out parameters truly do suck.)

And yes, once F# goes commercial, we plan on using it a lot, lot more. Actually, in _particular_ for webdev, since we can generate Javascript directly from F# instead of having to deal with Javascript. It's a major point of contention that F# is only about 1% of our codebase right now.
Wednesday, August 27, 2008 8:41:30 PM UTC
@Asbjørn

One of my goals this year is to "get" Ruby. I read most of Why's guide, talked to some Ruby people, but I just don't "get" it. Mainly what I mean is that I haven't seen anything in Ruby that warrants dynamic _typing_. I can understand being more succinct by having dynamic code emission, but that doesn't seem like a language issue necessarily (although it looks a lot nicer than System.Reflection.Emit).

In particular, things like having the database classes generated dynamically scare me. I change stuff around so often and so much and I _rely_ on the type system to catch what I missed. Replacing that with more unit tests doesn't seem correct. But again, I probably am just missing something. (Actually, what I _really_ _really_ don't get is Python. It doesn't seem as powerful as Ruby, and still suffers from no compile-time checking.)
Thursday, August 28, 2008 12:06:34 AM UTC
RE: Ruby

Some people find it useful, some don't?

Whatever your mind maps to best, maybe.

(I've more or less stopped using ASP.NET in favor of RoR, so maybe my mind maps to that best)
Thursday, August 28, 2008 2:00:12 AM UTC
@Arron

Oh, I can understand personal preference. I would completely accept if Ruby said "yea, we just like not having to declare our DB types in code up front" - but it seems to me that there is much, much more to it. But I haven't gotten to the point where it's come down to such a statement.

If it's a simple divergence of personal preference, that should be easy to state (just like people can say they hate type inference or like it). But there should also be logical reasoning about the benefits that should be accessible to me, even if I end up saying "I see, but yuck." I haven't found the "I see" part yet.
Thursday, August 28, 2008 5:45:53 AM UTC
I can't really say anything to logically justify Ruby's dynamic typing in that manner. :3

However, I'm one of those people that use metaprogramming a lot.

Here's an example of how Ruby's dynamic typing comes in handy for me, though.

In a C library I'm using, there's lots of pointer wrangling to get around the issue of inheritence:
[CODE]
struct Base {

int type;

}

struct Inherited {

int type;

int extraData;

}
[/CODE]

And used in functions like so:
[CODE]
return (struct Base*)inheritedInstance; // inheritedInstance is struct Inherited
[/CODE]
So I used Ruby to generate a lot of C helper methods in the form of:
[CODE]
Inherited* base_to_inherited(base*) { return (Inherited*)base; }
[/CODE]
And, inside of my Ruby code, that uses the C-to-Ruby marshalled types, whenever I receive a type that is marshalled as Base, I can call code like this:

base.to_inherited # returns Base* cast as Inherited*.

which under the hood is interpreted like this (some pseudo-code):
[CODE]
# we got here by catching method_missing and determing that to_inherited is not an actual method
return unless method_name ~= /to_/ (possibly a dynamic 'to_' call?)
possible_type = method_name[2..-1] //strip out the "to_" from "to_inherited" to leave "inherited"
instance_eval "NativeHelperMethods::base_to_#{possible_type}(self)"
[/CODE]
which would then invoke the underlying C helper methods. There's some boring-ass exception handling code, etc, left out.

I've done code like that in C# before using System.Reflection.Emit, but it was not the best use of my time. The best part about doing it Ruby is that it is very straight-forward; the metaprogramming only took me about 15 minutes to write and debug, and it was off to the races I was.
Thursday, August 28, 2008 6:10:24 AM UTC
Arron, thanks! Metaprogramming is the one thing I've heard that seems interesting, but most examples are usually pretty lame. On a current problem at work I was considering a few different approaches, some using Reflection.Emit, and decided against it cause it was too cumbersome in general.

I wonder how much is addressable from a library standpoint? Especially if the language supports representing the code as an AST and allows easy modifications on it?

I'll definitely look into this more. Thanks again.
Thursday, August 28, 2008 6:43:16 AM UTC
Seems like what you don't really understand is Rails and ActiveRecord then, not Ruby. :-) Dynamic typing has a lot to it, actually. You can find a lot of resources on what there is to like about it in Ruby, <a href="http://ruby-doc.org/whyruby">here</a>. The topic is a bit too large to dive into in a comment. :-)

I love the fact that you can make up, twist and change types as you go, and not have to plan everything up-front like you need to with a static type system. It's much more productive, at least in prototyping scenarios.

Where Ruby and Rails really shine, imo, is on documentation. Although Python has been around for much longer and most certainly has a larger fan and user base, Ruby still beats it hand down when it comes to documentation. Seems like Ruby's user base is more web-centric and web-aware or something, that might be helping Ruby in its online presence. Python on the other hand probably has a lot of users coming from the command line, which doesn't guarantee a great online presence. The web isn't best navigated by a "man" command, if you know what I mean.

To really like Ruby, or any other language for that matter, you need to use it, though. I've disliked most languages until I started using them for real (except for VB and TSQL, which I still hate), in projects where I can't just give up on it.
Thursday, August 28, 2008 6:45:53 AM UTC
I've never dipped into Ruby's raw AST stuff -- not sure it's even available from within Ruby. So much is exposed that I haven't learned a 3rd of everything yet. :)

I used to use a lot of System.Reflection for lazy, Regex-based parsing of incoming network messages (a really simple text-based protocol). It worked, 'automagically,' as I defined new network message types, but it was a real pain in the ass wrangling with System.Reflection -- but maybe it was because it was so verbose.

I think metaprogramming examples tend to be lame because when simplified there's not really a good justification for metaprogramming. For instance, the sample above, you'd probably ask, "why not just do: inherited = base_to_inherited(base)?"

The real beauty is the following pseudo-code:
// parse_message_type is a native C method.
// base is a marshaled Base*
base = parse_message_type(raw_string);
// there's a ton of constants in the form of 'BASE_TYPE_FOO' defining what kind of struct Base* really is.
// for instance, 'base.type == BASE_TYPE_INHERITED' would mean that Base* is really Inherited*
base_type_constants = NativeMethodHelpers.constants.collect { |method_name| method_name.starts_with('base_type') }
// find which class we really are.
for type in base_type_constants
if (instance_eval "NativeMethodHelpers::#{type} == base.type")
// a match -- return the proper structure
// type.class returns the constant name -- 'BASE_TYPE_FOO'
// so type.class.remove('BASE_TYPE') would just leave us with the type name.
// the following code below might look like "base.to_inherited"
return instance_eval "base.to_#{type.class.remove('base_type')}"

It's a 'no touch' solution. You can add as many new structures that inherit from struct Base as you'd like; the meta-programming does all the rest. You Just Code -- no room for errors, like a huge switch-case block where you might introduce a subtle bug by not paying attention (case 'base.type == BASE_TYPE_I_ALREADY_CHECKED_AGAINST_BECAUSE_I_WASNT_PAYING_ATTENTION')
Name
E-mail
Home page

Comment (HTML not allowed)  

Enter the code shown (prevents robots):

Live Comment Preview