Monday, February 19, 2007

kick it on DotNetKicks.com


Awhile back, a developer shared with me that C# 3.0 lambda expressions make him feel "lambdumb". Over the past several months, I've met several developers that start to look a bit nervous when they see the new "=>" operator. However, if you've stuck with me thus far on my current series (which presents functional programming from the vantage point of C# 2.0), there really isn't anything about lambda expressions that should worry you. This article is minor detour from that series to demonstrate what lambda expressions are all about.

On the surface, C# 3.0's lambda expressions are very similar in functionality to C# 2.0's anonymous methods. Just like anonymous methods, lambda expressions provide a way to declare code inline in a method body and allow the creation of lexical closures when external local variables are accessed by the inline code. (For more information on closures, see my article here.)

The most obvious advantage of lambda expressions over anonymous methods is their conciseness. For example, would you rather write this?

int[] numbers  = { 10, 1, 9, 2, 8, 3, 7, 4, 6, 5 };

int[] lessThanFive = Array.FindAll<int>(numbers,
  delegate(int n)
  {
    return
n < 5;
  });

int sum;
Array.ForEach<int>(lessThanFive,
  delegate(int n)
  {
    sum += n;
  });

Console.WriteLine(sum);

Or this?

int[] numbers = { 10, 1, 9, 2, 8, 3, 7, 4, 6, 5 };

int[] lessThanFive = Array.FindAll<int>(numbers, n => n < 5);

int sum;
Array.ForEach<int>(lessThanFive, n => sum += n);

Console.WriteLine(sum);

Getting rid of the clunky "delegate" keywords and the curly braces certainly does a great deal to reduce the code. However, I've spoken with some developers that have been confused by this new syntax so let's break it down step by step.

Here's the first anonymous method:

delegate(int n) { return n < 5; }

To convert it to a lambda expression, all we have to do is remove the "delegate" keyword and add the funky "=>" operator like so:

(int n) => { return n < 5; }

At this point, it should be clear that a lambda expression is still really a method. There is a parameter list and a method body just like an anonymous method but we've reduced the number of characters. Removing the "delegate" keyword reduced by 8 characters but adding the "=>" operator (and a space) gained 3 characters. So, by converting the anonymous method to a lambda expression, we've reduced the code by a total of 5 characters. Not bad, but we can do more.

(NOTE: Some have asked how we are supposed to pronounce the "=>" operator when reading code. I prefer to read it as "goes to". So, if I'm reading this lambda expression: x => x + 1, I would say "x goes to x plus one.")

Our next improvement is to allow the parameter "n" to by implicitly typed by the compiler like this:

(n) => { return n < 5; }

that change reduces the code by 4 more characters for a total of 9. You might be wondering if you could do the same this with an anonymous method? Well, you can't. Parameter type inference is a feature specific to lambda expressions. This results in a compiler error ("Identifier expected"):

delegate(n) { return n < 5; }

Next, the current C# 3.0 specification available from Microsoft states that "in a lambda expression with a single, implicitly typed parameter, the parentheses may be omitted from the parameter list." This means that, because our lambda expression only has one implicitly typed parameter, we can remove the parentheses like this:

n => { return n < 5; }

that gains us 2 more characters for a total of 11.

The last improvement is in the body of the lambda expression. It turns out that lambdas support two kinds of bodies: 1) an expression or 2) a statement block. Now, this is actually a bit confusing semantically because a single statement that returns nothing (or, "void") can be considered an expression. So, I prefer to think of these two body types as 1) a single statement and 2) multiple statements. In any case, if the lambda expression contains a single statement, it is legal to remove the curly braces and the "return" keyword (if any). If there is more than one statement, the braces and "return" keyword are required.

Since our lambda expression's body only consists of one statement, we can get rid of the curly braces and the "return" keyword like this:

n => n < 5

The reduces the code by an additional 12 characters for a total of 23 characters removed. If you do the math, you'll find that we've reduced the size of the original anonymous method by 70%! that's pretty impressive. However, for me the biggest improvement is that, on an English keyboard, I only have to press the shift key twice to type the lambda expression (for the '>' and '<' characters). The anonymous method version required pressing the shift key 5 times (for '(', ')', '{', '<' and '}'). that means that this lambda is much easier to type that its anonymous method. (This might not seem like a big deal but remember that I'm one of the developers on the CodeRush product so it's very important to me.)

OK, let's take a look at the second anonymous method from our sample code:

delegate(int n) { sum += n; }

This one doesn't reduce as much because there isn't a "return" keyword but we can still gain quite a bit.

First, remove the "delegate" keyword and add the "=>" operator to convert it to an anonymous method:

(int n) => { sum += n; }

Next, allow the "n" parameter to be implicitly typed:

(n) => { sum += n; }

Because this lambda expression only has one implicitly typed parameter, we can remove the parentheses:

n => { sum += n; }

Finally, since the body of this lambda expression only contains a single statement, we can remove the curly braces:

n => sum += n

The anonymous method version has 29 characters but the lambda expression only has 13. that's a code reduction of about 55%.

As you can see, brevity is a huge advantage that lambda expressions have over anonymous methods and we've really only scratched the surface. Lambda expressions really shine when using them in traditional functional programming techniques like currying (the simplest example yields a reduction of 75%).


Next time, we'll look at how lambda expressions improve the C# compiler's generic type inference.

kick it on DotNetKicks.com

posted on Monday, February 19, 2007 9:50:11 AM (Pacific Standard Time, UTC-08:00)  #    Comments [1]

kick it on DotNetKicks.com