Writing software is hard, particularly when the tools you use force you to think at too low a level; it’s time to start thinking about changing the way you write code… by making it easier to write code.
Taking on new ways to program doesn’t always mean tossing away your favorite programming language or environment. Sometimes it just means taking a new look at how you’re using your language and trying out a few new ideas. It’s time to take a hard look at your favorite language and see if it’s possible to “fall in love all over again”.
Many of you have undoubtedly run across Ruby before now, either a reference to it directly or its approach to Web programming (known as Ruby-on-Rails or just Rails). Some of you may even be concerned that Ruby threatens your job. Or some may be finding Ruby to be exciting and maybe (gasp) worth leaving .NET entirely.
A few years ago, after a few friends made similar kinds of transitions and suggested I do the same, I wrote…
“For many developers, it's been a while since they got together with their current programming environment. They've hit the 7-year-itch mark with their current language/platform partner. They find themselves in a rut. Coding is mundane. Routine. Boring, even. It's the same old roll-over, perfunctory foreplay about which frameworks to use, same decisions and scripts every time, same results, same good-night kiss and back-to-sleep as the last project, and the project before that and the project before that and the project before that...
“Ruby is new. Exciting. It makes you feel alive again. You feel appreciated. You feel loved. Like the language was made just for you. It caresses your desires, gives you new ideas, molds itself to what you want it to be. It makes your jaw drop and say, "I didn't know you could do that!". It leaps to your will, and does so much more than you thought a partner could do. You wonder what you ever saw in that language you left behind.
“At least at first.
“Over time, though, the infatuation ends as most affairs do-in time, you discover a certain comfort in your language of choice. Sure, it's not perfect, but you know it well, you can get the job done, and what's more, everybody's content. Not ecstatically happy, sure, but "good enough", and besides, it's hard work trying to learn the nuances of a new partner. Nobody likes to admit it, but sometimes comfortable is better than exciting.
“You never forget those heady days, feeling the wind in your hair and reliving your younger days as a programmer. It reinvigorates you, reenergizes you, makes you feel alive. It gives you something you didn't know you needed, but in the end, fires you up to go back to what you know best, brimming with fresh ideas and energy, ready to spice up your partnership so that you can remain happy for the next five, ten, even twenty years.
“Ruby is a love affair.”
What I failed to mention as part of that blog post is that there is, of course, a third option: taking what you learned from your extravagant “fling” and “spicing up your life” with your current partner… I mean language… of choice.
Consider, for example, the current interest in functional programming languages, a la F# or Haskell or Erlang. It turns out that not only does the C# 3.0 language have some interesting functional goodies in it, but the same is true of C# 2.0, that same C# 2.0 that you’ve been using the past four years to do all that “object-oriented” stuff.
Getting Functional
The first step to using C# 2.0 in any kind of functional manner is to understand what it really means to be “functional” in nature. At their heart, all functional languages have a single concept that marks them apart from the other languages, and that is the idea that functions are first-class citizens in the language, just as objects are first-class citizens in C# or Java. Functions can be created easily, passed around easily, and executed easily, without requiring significant effort. There’s more to the story than just that, but that’s the nutshell version.
Part of this “function” mindset comes with an implicit realization that this is very similar to how math works, and with that, comes a mindset that says “don’t modify your inputs, create a new set of things to work with”. For example, in a traditional O-O program, if we want to take a selection of objects and perform some operations on a small group of them, the typical way is to do something along the following:
class Program
{
private static IList<Person> persons = ...;
static void Main(string[] args)
{
// Who's having a mid-life crisis?
foreach (Person p in persons)
{
if (p.Age > 35)
Console.WriteLine("Cheer up! " +
"You're not dead yet!");
}
}
}
Now while this certainly isn’t terrible code, in of itself, the idea behind what we’re trying to do here is a bit lost in some of the surrounding structure; in other words, the criteria by which we filter the list is tangled up together with the action we want to take against the resulting filtered list of Person objects.
A functional approach suggests that, instead, we want a function (typically a top-level function in the traditional functional languages, but we can add a bit of O-O discipline here) that will take a List of any type, iterate through it, apply a function to each element in the list, and if that “evaluation” function returns true, capture that for the list of returned elements. In of itself, it doesn’t sound too hard, except for that “apply a function” part-how do we pass a function in?
Given that we’re in C#, the answer should already be somewhat self-evident-any time a “function” is mentioned, the word “delegate” springs to mind. Remember, delegates are essentially objects wrapped around a function, turning the combination into what we, back in the good ol’ days of C++, used to call a functor, or sometimes a function object. Whatever you choose to call it, it serves our purpose here.
Having said that, though, we need to create a delegate type that will be typesafe-we could go ahead and create this “filter” function to take Objects, but then we lose some of the benefits of knowing that it’s a Person we’re going to evaluate. This sounds like a job for a generic:
public delegate bool FilterFunc<T>(T arg);
Now, we’ve got the basics in place to write this “Filter” function on some helpful static utility class:
public static class List
{
public static IList<T> Filter<T>(IList<T> src,
FilterFunc<T> eval)
{
IList<T> results = new List<T>();
foreach (T item in src)
if (eval(item))
results.Add(item);
return results;
}
}
Using it, though, means that now we have to capture the criteria as a separate method, a la:
static void Main(string[] args)
{
IList<Person> oldPeople =
List.Filter(persons, IsOverForty);
foreach (Person p in oldPeople)
Console.WriteLine("Don't worry, " +
p.Name + " at least you " +
"have your health!");
}
static bool IsOverForty(Person p)
{ return p.Age > 40; }
This doesn’t look like it’s a whole lot better than the first version, after all. The first version only took a couple of lines of code, and, quite honestly, seemed a lot easier to use.
But we’re not quite done yet. Filtering isn’t the only thing we want to do on lists-look at the above code again. We want to filter the list, yes, but we also want to act on each element in the list. In functional terms, this goes by various names, so we’ll call it a generic “Act” function:
public delegate void ActionFunc<T>(T arg);
public static class List
{
// . . .
public static void Act<T>(IList<T> src,
ActionFunc<T> act)
{
foreach (T item in src)
act(item);
}
}
And now the code in Main() becomes a bit clearer… sort of:
static void Main(string[] args)
{
// Who's having a mid-life crisis?
IList<Person> oldPeople =
List.Filter(persons, IsOverForty);
List.Act(oldPeople, PrintConsolation);
}
static bool IsOverForty(Person p)
{ return p.Age > 40; }
static void PrintConsolation(Person p)
{
Console.WriteLine("Don't worry, {0}, " +
"at least you have your health!",
p.Name);
}
But it’s still pretty awkward to have to create a new method for each element we want to pass in to the various functional points… which is where anonymous methods can make things much easier:
static void Main(string[] args)
{
// Who's having a mid-life crisis?
IList<Person> oldPeople =
List.Filter(persons,
delegate(Person p) { return p.Age > 40;
});
List.Act(oldPeople,
delegate(Person p)
{
Console.WriteLine("Oh, forget it." +
" You're gonna die.");
});
}
Readers familiar with C# 3.0 will no doubt quickly point out that all of this seems particularly familiar, and yes, you’re right: all of this is vaguely LINQ-like. In fact, a number of features were inserted into the C# 3.0 language specifically to make this kind of coding (which is what LINQ essentially does, if you drill down deep enough) easier. Lambda expressions, for example, make it much easier to write the anonymous methods that are passed in, and extension methods allow us to “slip” the new definitions of Filter and Act onto the List<T> definition itself, making it possible to write code along the lines of:
static void Main(string[] args)
{
// Who's having a mid-life crisis?
persons.Filter((p) => return p.Age > 40))
.Act((p) => Console.WriteLine("Oh, forget"
+ " it. You're gonna die.")));
}
Which, you have to admit, is a lot easier to read.
As a final enhancement, we could take advantage of the C# “yield return” to do all this in terms of IEnumerable<T>s, thus reducing the code even further, and introducing a sense of “laziness” to the execution of those blocks… But that’s an enhancement that we’ll let curious readers either work through on their own. (Or you could read through the code examples we’ve cooked up, available at http://code-url-here.)
On top of all this, even in the C# 2.0 style, a couple of additional benefits come to place.
First, and this may seem a minor benefit, but don’t underestimate the basic fact that we’re now gathering a common programming idiom into a first-class concept. Remember those old procedural adages that said, “Take commonly-written code and put it in one place, so it’s easier to maintain and modify later?” We’ve just done that.
Second, because we’re making a copy of the list, not modifying the list itself, no thread-safety concerns raise their ugly heads. Granted, the objects inside the list are now shared between two lists, and that could present a problem later; if it does, we create a “DeepCopyFilter” or similar kind of function that clones the contents of the list before returning the results, and thus gain the added benefit of making it more clear what we’re doing when we filter the list in addition to the increased isolation and safety.
Third, as you see above, particularly for APIs that are under our control, we can create “fluent APIs” (see Neal Ford’s article on the subject in the ZZZ issue of CoDe) that begin to read almost like SQL statements-“Filter by this criteria, then Act on each of those results”, and so on.
Fourth, although we don’t show it here, the consumer of the Filter and Act functions can just as easily be VB or C++/CLI, something that couldn’t be said about the original “foreach” approach-each language has to provide its own language construct. For something as fundamental as for loops, that may not seem like a real win, but think back to the early days of C# and VB, when C# received the “using” keyword… and VB didn’t. Had “using” instead been a functionally-designed function, VB developers wouldn’t have had to wait for several years to take advantage of its resource-safe nature.
Remember, a programming language doesn’t constrain you from adopting a new mindset, so take advantage of that and start exploring.