Chris Marinos


January 08, 2010

Don’t Misuse Lambdas

Avoid Duplicating Code

It’s great that so many C# and VB.NET developers are taking advantage of LINQ. Unfortunately, using LINQ can encourage you to misuse lambdas. Consider the following simple example:

var results = from x in 0.Through(10)
 select x * x;

Square is a useful utility function. It shouldn’t be defined as a lambda because that ensures code duplication instead of reuse. It’s more obvious using extension method-syntax:

var results =
 0.Through(10)
 .Select(x => x * x);

The problem is that in versions of C# prior to 4.0, it is painful to define Square as a reusable method because Select is a function with multiple generic parameters. You are forced to painfully specify the type parameters or use an unnecessary lambda:

//specifying the generic types
var results =
 0.Through(10)
 .Select<int, int>(Math.Square);
 
//using an unnecessary lambda
var results =
 0.Through(10)
 .Select(x => Math.Square(x));

Even in C# 4.0, the query syntax retains the unnecessary lambda problem:

var results = from x in 0.Through(10)
 select Math.Square(x);

Fortunately, the extension method syntax is fixed in 4.0:

var results =
 0.Through(10)
 .Select(Math.Square);

Avoid Multi-Line Lambdas

It’s easy to recognize that Square should not be a lambda, but some functions are less obvious:

IEnumerable<Ninja> MakeFearsomeFightingTeam(SecretOoze ooze,
 IEnumerable<Turtle> turtles,
 Pizza pizza)
{
 return 
 turtles
 .Select(turtle =>
 {
 var transformed = ooze.Transorm(turtle);
 transformed.Say("Cowabunga!");
 transformed.Eat(pizza);
 return transformed;
 });
}

You should avoid writing multi-line lambdas like this where possible. In this case, it makes the code harder to read, especially if you add another operation after the Select. It’s difficult to understand what the lambda is doing upon first glance. Also, there’s a good chance that you will want to reuse the behavior of the lambda elsewhere in your program.

IEnumerable<Ninja> MakeFearsomeFightingTeam2(SecretOoze ooze,
 IEnumerable<Turtle> turtles,
 Pizza pizza)
{
 return
 turtles
 .Select(turtle => Ninjaify(ooze, pizza, turtle));
}
 
Ninja Ninjaify(SecretOoze ooze, Pizza pizza, Turtle turtle)
{
 var transformed = ooze.Transorm(turtle);
 transformed.Say("Cowabunga!");
 transformed.Eat(pizza);
 return transformed;
}

The named instance method gives a description to the behavior that was in the multi-line lambda, and the Select statement is more readable. The sacrifice is that Ninjaify has to take extra arguments because it cannot rely on closure. There is also no guarantee that the definition of Ninjaify will remain close to where it is used as you add more code. Additionally, C#’s syntax requires that you use an unnecessary lambda to pass the turtle argument to Ninjaify. The answer is to use a locally defined method:

IEnumerable<Ninja> MakeFearsomeFightingTeam3(SecretOoze ooze, 
 IEnumerable<Turtle> turtles,
 Pizza pizza)
{
 Func<Turtle, Ninja> Ninjaify = 
 turtle => 
 {
 var transformed = ooze.Transorm(turtle);
 transformed.Say("Cowabunga!");
 transformed.Eat(pizza);
 return transformed;
 };
 
 return
 turtles
 .Select(Ninjaify);
}

Like a lambda, the locally defined method uses closure to avoid redefining variables, and it has the readability benefit of being defined near to where it is used. Since it is a named method, it provides a useful description of it’s behavior, and it doesn’t disrupt the flow of your Select method. It’s the best of both worlds. The only downside is that it can’t be called outside the scope of the MakeFearsomeFightingTeam3 method.

Unfortunately, C# requires you to write the full type signature for the function. The compiler will fail with a “cannot assign lambda expression to implicitly-typed local variable” if you try to use the var keyword. This is a real pain for more complicated signatures, and it limits you from using locally defined methods to their full potential.

Going Further With F#

In F#, the type signature problem goes away:

    let MakeFearsomeFightingTeam4 ooze turtles pizza =
        let ninjaify turtle =
            let transformed = ooze.Transform(turtle)
            transformed.Say("Cowabunga!")
            transformed.Eat(pizza)
            transformed

        turtles
        |> Seq.map ninjaify

Here, you can see the benefit of F#’s type inference system and functional programming roots. The behavior of the code is retained, but the extraneous type signature is not required.

F# also fixes the problem of requiring an unnecessary lambda for named methods outside the scope of the MakeFearsomeFightingTeam4 method. Automatic currying makes it easy to pass the arguments that would be captured by closure:

      let ninjaify ooze pizza turtle = 
        let transformed = ooze.Transform(turtle) 
        transformed.Say("Cowabunga!") 
        transformed.Eat(pizza) 
        transformed

    let MakeFearsomeFightingTeam5 ooze turtles pizza = 
        turtles
        |> Seq.map (Ninjaify ooze pizza)

In Summary

  • Use lambdas for one line, single use functions
  • Prefer locally defined functions to multi-line lambdas, but be wary of complicated type signatures in C#
  • If a function is reusable, move it to the appropriate class or module, and use it instead of a lambda or locally defined function
  • Use F# to avoid messy type signatures and unnecessary lambdas
Topic Tags:  , ,

November 24, 2009

It’s Beta For a Reason!

Today, I was in the process of creating a branch of Elevate to support .NET 4.0 when I came across a subtle, but breaking change in the Enumerable.Count function. The following code works in .NET 3.5, but fails in .NET 4.0 Beta 2:

[TestClass]
public class CountBug
{
 public class MyIList : IList<int>
 {
 public MyIList()
 {
 GetEnumeratorWasCalled = false;
 CountWasCalled = false;
 }
 
 //...non-relavent IList members excluded for this post...
 
 public bool GetEnumeratorWasCalled { get; set; }
 
 public IEnumerator<int> GetEnumerator()
 {
 GetEnumeratorWasCalled = true;
 return null;
 }
 
 public bool CountWasCalled { get; set; }
 
 public int Count
 {
 get
 {
 CountWasCalled = true;
 return 0;
 }
 }
 }
 
 [TestMethod]
 public void CountBehavior()
 {
 var list = new MyIList();
 Assert.AreEqual(0, list.Count());
 Assert.IsTrue(list.CountWasCalled);
 Assert.IsFalse(list.GetEnumeratorWasCalled);
 }
}

 

The Enumerable.Count method in .NET 4.0 Beta 2 fails to recognize that MyIList implements ICollection<T>, so instead of returning the .Count property, it calls the MyIList’s GetEnumerator() method and walks every item in the list to determine the count. In .NET 3.5, MyIList is identified as an ICollection<T> implementer and the .Count property is correctly used.

It’s also worth noting that it does not seem to be possible to create a MSTest test project in Visual Studio 2010 Beta 2 that targets .NET 3.5. It will silently upgrade any of your .NET 3.5 test projects to .NET 4.0 even if you select 3.5 as the target framework when creating a project!

I’ve filed the following bugs on connect, so hopefully they get resolved soon!

https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=514122

[update: I initially posted a link to the wrong bug. The above link has been corrected.]

https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=514130


November 23, 2009

Designing F# Functions for Currying and the |> Operator

Last week, I led a jam about F# at the Ann Arbor Study Group. One of my SRT Solutions coworkers, Ben Barefield, asked a question that warrants further discussion. After I introduced the forward pipe (|>) operator, Ben asked the following:

In F# programming, do you design functions so the last argument is one that you intend for users to pass via the forward pipe operator?

My first response was a tentative “yes”, but I felt like that put too much focus on the forward pipe operator. After some reflection, I think a better answer is to follow this more general best practice:

In F# programming, prefer ordering function arguments from least varying to most varying.

Normally, you’ll see this discussed in the context of currying and partial application, but I think that it is equally important when considering the forward pipe operator. Let’s take a look at some examples of each.

Currying and Partial Application

We’ll start with the Seq.reduce function. The signature for this function is:

Seq.reduce : ('T -> 'T -> 'T) -> seq<'T> -> 'T

The F# documentation states that reduce is used to “Apply a function to each element of the sequence, threading an accumulator argument through the computation.” In practice, reduce is used to compute a single value from a sequence of values. For example:

> Seq.reduce (+) {0..5};;
val it : int = 15

Here, the computation starts with the first two elements of the sequence, 0 and 1. Reduce applies the addition function to these elements to return 1. This is now our current “state” which we carry over into the next step of computation. Reduce will grab the next element in the list, 2, and call our addition function with that argument and our current state of 1 to produce 3. This process continues until we get our result of 15.

Now that we know how reduce works, observe that the arguments are structured from least varying to most varying. When viewed from the standpoint of currying this is handy because it allows us to create useful residual functions through partial application:

> let mySum<'a> = Seq.reduce (+);;

val mySum<'a> : (seq<int> -> int)

> mySum {0..5};;
val it : int = 15

The |> Operator

In F#, it’s common to rewrite the first example from above using the forward pipe operator:

> {0..5}
   |> Seq.reduce (+);;
val it : int = 15

This compatibility with the forward pipe operator also comes naturally as a result of ordering arguments from least varying to most varying. Because the last argument is the one that is most likely to vary, it follows that it is also the argument that we are most likely to pass via the forward pipe operator.

Topic Tags:  , ,

Syndication OptionsRSS (Rich Site Summary) Feed Atom Feed OPML (Outline Processor Language) Feed MYST-ML (MyST Markup Language) Content Feed MS-Office Smart Tag Subscription