Monday, October 15, 2007

Ruby-style loops in C# 3.0

Ruby has a pretty interesting (and succinct) way of looping through a set of numbers:

5.times do |i|
  print i, " "
end

The results of executing this Ruby block is:

0 1 2 3 4

I really love the readability and conciseness of this syntax, just enough to see what's going on, but not a lot of extra stuff to get in the way.  In addition to the "times" method, there's also "upto" and "downto" methods for other looping scenarios.

Some slight of hand

With extension methods and lambda expressions and C# 3.0, this form of loop syntax is pretty easy to do.  Not really a great idea, but at least an example of what these new constructs in C# 3.0 can do.

So how can we create this syntax in C#?  The "times" method is straightforward, that can just be an extension method for ints:

public static void Times(this int count)

Now this method shows up in IntelliSense (when I add the appropriate "using" directive):

Now that the Times method shows up for ints, we can focus on the Do block.

Adding the loop behavior

Although I can't declare blocks in C# 3.0, lambda expressions are roughly equivalent.  To take advantage of lambda expressions, I want to give the behavior to the Times method in the form of a delegate.  By declaring a delegate parameter type on a method, I'm able to use lambda expressions when calling that method.

I didn't like passing the lambda directly to the "Times" method, so I created an interface to encapsulate a loop iteration, faking the Ruby "do" block with methods:

public interface ILoopIterator
{
    void Do(Action action);
    void Do(Action<int> action);
}

Now I can return an ILoopIterator from the Times method instead of just "void".  Now the final part is to create an "ILoopIterator" implementation that will do the actual looping:

private class LoopIterator : ILoopIterator
{
    private readonly int _start, _end;

    public LoopIterator(int count)
    {
        _start = 0;
        _end = count - 1;
    }

    public LoopIterator(int start, int end)
    {
        _start = start;
        _end = end;
    }  

    public void Do(Action action)
    {
        for (int i = _start; i <= _end; i++)
        {
            action();
        }
    }

    public void Do(Action<int> action)
    {
        for (int i = _start; i <= _end; i++)
        {
            action(i);
        }
    }
}

public static ILoopIterator Times(this int count)
{
    return new LoopIterator(count);
}

I let the "LoopIterator" class encapsulate the behavior of performing the underlying "for" loop and calling back to the Action passed in as a lambda.  It makes more sense when you see some client code calling the Times method:

int sum = 0;
5.Times().Do( i => 
    sum += i
);
Assert.AreEqual(10, sum);

That looks pretty similar to the Ruby version (but not quite as nice), but it works.  Compare this to a normal loop in C#:

int sum = 0;
for (int i = 0; i < 5; i++)
{
    sum += i;
}
Assert.AreEqual(10, sum);

Although the "for" syntax is functional and about the same number of lines of code, the Ruby version is definitely more readable.  Adding additional UpTo and DownTo methods would be straightforward with additional ILoopIterator implementations.

Feature abuse

Yeah, I know this is more than a mild case of feature abuse, but it was interesting to see the differences between similar operations in C# 3.0 and Ruby.  Although it's possible to do these similar operations, with similar names, this example highlights how much the syntax elements of the static CLR languages can get in the way of a readable API, and how much Ruby stays out of the way.

3 comments:

Thrawn said...

Terrible. You said it, you engaged in feature abuse. I also don't see how you can describe the opaque Ruby snippet as "readable". It does not abide by any syntax of colloquial logic terminology!

Very interesting C# 3.0 read though :)

Anonymous said...

Actually it does, you should work this out in SCHEME or LISP and read about lambda calculus and you would see that the ruby AND C# (although verbose) makes sense. If you have no clue what i'm talking about send me an IM snuuuggs. (by the way i'm actually a C# developer but i read programs in a mathematical form and the RUBY code IS actually more like the base math under the engine)

Adi said...

Oes Tsetnoc one of the ways in which we can learn seo besides Mengembalikan Jati Diri Bangsa. By participating in the Oes Tsetnoc or Mengembalikan Jati Diri Bangsa we can improve our seo skills. To find more information about Oest Tsetnoc please visit my Oes Tsetnoc pages. And to find more information about Mengembalikan Jati Diri Bangsa please visit my Mengembalikan Jati Diri Bangsa pages. Thank you So much.
Oes Tsetnoc | Semangat Mengembalikan Jati Diri Bangsa