Most articles about closures are written in terms of functional languages, as they tend to support them best. However, that's also precisely why it's useful to have an article written about how they appear more traditional OO languages. Chances are if you're writing in a functional language, you know about them already. This article will talk about C# (versions 1, 2 and 3) and Java (before version 7).
What are closures?
To put it very simply, closures allow you to encapsulate some behaviour, pass it around like any other object, and still have access to the context in which they were first declared. This allows you to separate out control structures, logical operators etc from the details of how they're going to be used. The ability to access the original context is what separates closures from normal objects, although closure implementations typically achieve this using normal objects and compiler trickery.
It's easiest to look at a lot of the benefits (and implementations) of closures with an example. I'll use a single example for most of the rest of this article. I'll show the code in Java and C# (of different versions) to illustrate different approaches. All the code is also available for download so you can tinker with it.
Example situation: filtering a list
It's reasonably common to want to filter a list by some criterion. This is quite easy to do "inline" by just creating a new list, iterating over the original list and adding the appropriate elements to the new list. It only takes a few lines of code, but it's still nice to hide that logic away in one place. The difficult bit is encapsulating which items to include. This is where closures come in.
Although I've used the word "filter" in the description, it's somewhat ambiguous between filtering items in the new list and filtering things out. Does an "even number filter" keep or reject even numbers, for instance? We'll use a different bit of terminology - a predicate. A predicate is simply something which matches or doesn't match a given item. Our example will produce a new list containing every element of the original list which matches the given predicate.
In C# the natural way of representing a predicate is as a delegate, and indeed .NET 2.0 contains a Predicate
// Declaration for System.Predicate<T>
public delegate bool Predicate<T>(T obj)
// Predicate.java
public interface Predicate<T>
{
boolean match(T item);
}
The code used to filter the list is very straightforward in both languages. I should point out at this stage that I'm going to steer clear of extension methods in C# just to make the example simpler - but anyone who has used LINQ should be reminded of the Where extension method. (There are differences in terms of deferred execution, but I'll avoid those for the moment.)
// In ListUtil.cs
static class ListUtil
{
public static IList<T> Filter<T>(IList<T> source, Predicate<T> predicate)
{
List<T> ret = new List<T>();
foreach (T item in source)
{
if (predicate(item))
{
ret.Add(item);
}
}
return ret;
}
}
// In ListUtil.java
public class ListUtil
{
public static <T> List<T> filter(List<T> source, Predicate<T> predicate)
{
ArrayList<T> ret = new ArrayList<T>();
for (T item : source)
{
if (predicate.match(item))
{
ret.add(item);
}
}
return ret;
}
}
(In both languages I've included a Dump method in the same class which just writes out the given list to the console.)
Now that we've defined our filtering method, we need to call it. In order to demonstrate the importance of closures, we'll start with a simple case which can be solved without them, and then move on to something harder.
Comments