LINQ: declarative and functional syntax

In my last post about LINQ I shown you an example about how to use the language integrated query to select information about directories. In this post I’ll get more into the syntax and show you something about the functional querying style.

My examples will focus on displaying information about UEFA Champions Leage winners. Thus, I have created a class called Winner that looks this this:

class Winner
{
    string  _name;
    string  _country;
    int     _year;

    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }
    public string Countryr
    {
        get { return _country; }
        set { _country = value; }
    }

    public int Year
    {
        get { return _year; }
        set { _year = value; }
    }

    public Winner(string name, string country, int year)
    {
        _name = name;
        _country = country;
        _year = year;
    }
};

Also I have created a utility class that returns a list (incomplete) of UCL winners:

class UCL
{
    public static IEnumerable<Winner> GetWinners()
    {
        Winner [] winners = 
        {
            new Winner("Barcelona", "Spain", 2006),
            new Winner("Liverpool", "England", 2005),
            new Winner("FC Porto", "Portugal", 2004),
            new Winner("AC Milan", "Italy", 2003),
            new Winner("Real Madrid", "Spain", 2002),
            new Winner("Bayern Munchen", "Germany", 2001),
            new Winner("Real Madrid", "Spain", 2000),
            new Winner("Manchester Utd.", "England", 1999),
            new Winner("Real Madrid", "Spain", 1998),
            new Winner("Olimpique Marseille", "France", 1993),
        };

        return winners;
    }
};

Now, let’s see how we could display all this info ascending by the year of winning:

IEnumerable<Winner> winners = UCL.GetWinners();
var result = from w in winners
             orderby w.Year
             select w;

foreach (var w in result)
{
    Console.WriteLine("{0}\t{1}, {2}",
            w.Year, w.Name, w.Country);
}

That lists the following:

1993    Olimpique Marseille, France
1998    Real Madrid, Spain
1999    Manchester Utd., England
2000    Real Madrid, Spain
2001    Bayern Munchen, Germany
2002    Real Madrid, Spain
2003    AC Milan, Italy
2004    FC Porto, Portugal
2005    Liverpool, England
2006    Barcelona, Spain

This SQL-like syntax is however only a “shell” over the functional syntax. It’s just like with the foreach statement. To be able to iterate with foreach, the collection must implement IEnumerable, which has a method that returns a class that implements IEnumerator. What the compiler is doing when using foreach is calling GetEnumerator to get an iterator, and then Reset() on it, and inserts a while(iterator.MoveNext()), using Current to access the current object from the collection. The same happens here with the declarative query syntax.

Now, let’s suppose we want to list only the winners from England. What we have to do is adding a filtering:

var result = from w in winners
             orderby w.Year
             where w.Country == "England"
             select w;

What I wrote above is actually the same with:

var result = winners.
               OrderBy(w => w.Year).
               Where(w => w.Country == "England").
               Select(w => w);

Here we used the operators OrderBy, Where and Select. These are two of the query operators that allow you to perform filtering, projection and key extraction. These are built of the concept of Lambda expression, which are similar to the CLR delegates. We could rewrite the last query list this:

Func<Winner, bool> filter = w => w.Country == "England";
Func<Winner, int> criteria = w => w.Year;
Func<Winner, Winner> project = w => w;

var result = winners.OrderBy(criteria).Where(filter).Select(project);

OrderBy and OrderByDescending are operators that impose a partial order over the keys. Operators ThenBy and ThenByDescending are used to apply additional sorting criteria by only on sorted sequences (SortedSequence).

Where is used to exclude items from the collection.

Select and SelectMany are operators for projecting only those fields or info that is wanted.

Now, let’s try something more complicated: grouping the winners by country, and inside each group ascending by the winning year. In declarative syntax that would be like this:

var result = from w in winners
             orderby w.Year
             group w.Name by w.Country into groups
             orderby groups.Key
             select groups;

foreach (var w in result)
{
     Console.WriteLine("\n{0}", w.Key);

     foreach (var e in w)
     {
        Console.WriteLine("{0}", e);
     }
}

Enagland
Manchester Utd.
Liverpool

France
Olimpique Marseille

Germany
Bayern Munchen

Italy
AC Milan

Portugal
FC Porto

Spain
Real Madrid
Real Madrid
Real Madrid
Barcelona

In functional programming syntax, the same query is written as:

var result = winners.
                  OrderByDescending(w => w.Year).
                  GroupBy(w => w.Country, w => w.Name).
                  OrderBy(w => w.Key).
                  Select(w => w);

The GroupBy operator imposes a partitioning over a sequence of values based on a key extraction function. It returns a sequence of Grouping values, which contains both the key as well as the group of values mapped to the key. The interface of Grouping is:

public sealed class Grouping<K, T>{
  public Grouping(K key, IEnumerable<T> group);
  public Grouping();
  public K Key {get; set;}
  public IEnumerable<T> Group {get; set;}
}

You may wonder why the declarative syntax starts with the from clause. The SQL language has a problem, i.e. the order of clauses is not natural. Select is the first by at the time of selecting, you don’t know where do you select from. The from clause is naturally the first one, and this was rectified in LINQ. You always have to put from as the first clause in a query (when using the declarative syntax). In the functional syntax you can see you have to apply the operators on a collection, a sequence of items (in our case winners). That is the functional equivalent of from.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.