navigation
 Thursday, May 31, 2007

A higher-order function is defined as a function that returns a function as a result. Higher-order functions are common in the world of functional programming, but have only recently started to become more mainstream. As of C# 2.0, they are starting to become part of the language. A perfect example of their use would be with the generic List<T>.FindAll() method, which takes a Predicate<T> as an argument. A Predicate<T> is a delegate, which is a strongly typed method reference. One way to search for all the items in a list is to use anonymous delegates. For example, given a list of Falafel employees:

List<Falafel> falafels = new List<Falafel>();
falafels.Add( new Falafel( "Lino", "Tadros" ) );
falafels.Add( new Falafel( "John", "Waters" ) );
falafels.Add( new Falafel( "Noel", "Rice" ) );
falafels.Add( new Falafel( "Adam", "Anderson" ) );
falafels.Add( new Falafel( "Xavier", "Pacheco" ) );
falafels.Add( new Falafel( "Adam", "Markowitz" ) );
falafels.Add( new Falafel( "Mike", "Dugan" ) );
falafels.Add( new Falafel( "Bary", "Nusz" ) );
falafels.Add( new Falafel( "Rick", "Miller" ) );
falafels.Add( new Falafel( "Mike", "Saad" ) );
falafels.Add( new Falafel( "Eric", "Titolo" ) );

You could search for all employees whose name starts with "A" like this:

falafels.FindAll( delegate( Falafel item ) { return item.FirstName.StartsWith( "A" ); } )

Output:
Adam Anderson
Adam Markowitz

However, if you wanted to perform another search for all employees whose name starts with "M", you'd have to write an entirely different anonymous delegate. That's where higher-order functions can help. Let's create a higher-order function that will return a delegate that searches for Falafels whose first name starts with a string we pass as a parameter:

public static class Predicates
{
  public static Predicate<Falafel> FirstNameStartsWith( string s )
  {
    return delegate( Falafel item ) { return item.FirstName.StartsWith( s ); };
  }
}

Now we can search for employees whose name starts with any letter we want:

falafels.FindAll( Predicates.FirstNameStartsWith( "A" ) );
falafels.FindAll( Predicates.FirstNameStartsWith( "M" ) );

Output:
Adam Anderson
Adam Markowitz

Mike Dugan
Mike Saad

But the fun doesn't stop there! If you define a set of primitive Predicates, you can define more complex Predicates as a combination of the simpler ones. Here is how to define higher-order functions that take a list of Predicates and returns a Predicate that returns the result of evaluating each of them and combining the result with the logical AND or OR operator:

public static Predicate<Falafel> And( params Predicate<Falafel>[] predicates )
{
  return delegate( Falafel item )
  {
    foreach ( Predicate<Falafel> predicate in predicates )
      if ( !predicate( item ) )
        return false;
    return true;
  };
}

public static Predicate<Falafel> Or( params Predicate<Falafel>[] predicates )
{
  return delegate( Falafel item )
  {
    foreach ( Predicate<Falafel> predicate in predicates )
      if ( predicate( item ) )
        return true;
    return false;
  };
}

Using these, we can create a single Predicate that searches for all Falafels whose first name starts with "A" or "R" and whose last name starts with "M":

Predicate<Falafel> predicate = Predicates.Or( Predicates.FirstNameStartsWith( "A" ), Predicates.FirstNameStartsWith( "R" ) );
predicate = Predicates.And( predicate, Predicates.LastNameStartsWith( "M" ) );
falafels.FindAll( predicate );

Output:
Adam Markowitz
Rick Miller

Higher-order functions are a powerful abstraction, and they're only going to become more common and easier to write with C# 3.0 lambda expressions, so give them a try and see how they can help make your code more concise and expressive.

Name
E-mail
Home page

Comment (Some html is allowed: a@href@title, i, strike, u) where the @ means "attribute." For example, you can use <a href="" title=""> or <blockquote cite="Scott">.  

Enter the code shown (prevents robots):

Live Comment Preview