Last week I went to a talk presented by Mike Wagg and Mark Needham from ThoughtWorks on Mixing Functional and Object-Oriented Approaches to Programming in C#. Mike and Mark discussed using a functional approach with LINQ to solve problems in C#.
I have come to realise lately that some problems are much better suited to a functional approach than traditional imperative programming. Many problems that involve selecting, filtering or performing actions on a list of items are best suited to functional programming, which can significantly reduce the amount of code required to solve the problem.
Here is a simple example of a problem that is best solved with a functional rather than an imperative approach.
Some businesses advertise their phone number as a word, phrase or combination of numbers and alpha characters. This is easier for people to remember than a number. You simply dial the numbers on the keypad that correspond to the characters. For example, “1-800 FLOWERS” translates to 1-800 3569377.
We will write a simple program that translates a word into a list of corresponding numbers.
First, let’s start with a dictionary that contains each number and the corresponding characters:
private readonly Dictionary<int, char[]> keys =
new Dictionary<int, char[]>()
{
{1, new char[] {}},
{2, new[] {'a', 'b', 'c'}},
{3, new[] {'d', 'e', 'f'}},
{4, new[] {'g', 'h', 'i'}},
{5, new[] {'j', 'k', 'l'}},
{6, new[] {'m', 'n', 'o'}},
{7, new[] {'p', 'q', 'r', 's'}},
{8, new[] {'t', 'u', 'v'}},
{9, new[] {'w', 'x', 'y', 'z'}},
{0, new[] {' '}},
};
Next, we create a Translate method that takes a word and returns an array of corresponding numbers.
With a traditional, imperative approach, we would use for-each loops and if-statements to iterate through characters and populate an array of matching numbers:
public int[] Translate(string word)
{
ArrayList numbers = new ArrayList();
foreach (char character in word)
{
foreach(KeyValuePair<int, char[]> key in keys)
{
foreach(char c in key.Value)
{
if(c == character)
{
numbers.Add(key.Key);
}
}
}
}
return (int[]) numbers.ToArray(typeof (int));
}
Alternatively, with a functional approach we can use LINQ to select elements from the dictionary and transform the output to an array of matching numbers:
public int[] Translate(string word)
{
return word.Select(c =>
keys.First(k => k.Value.Contains(c)).Key).ToArray();
}
And there you have it. Several lines of nested for-each loops replaced with a single line of succinct functional code. Much nicer!
Great post Tim although I would say it’s quite a big jump for those new to functional programming to just convert 3 nested foreach loops & an if statement to that single line of functional code.
Are there any individual steps you could show that you went through to get to that final state?
Hi Paul, thanks for your comment. You are right! I’m now working on a blog post that explains the steps I took to refactor the code.
I enjoyed the post (and the next one, which dissects the refactoring), I feel like it’s worth mentioning that both the imperative and functional solutions could’ve been made simpler by inverting the keys Dictionary – if it were constructed with a-z and space as keys and 0, 2, 3, …, 9 as values, the original loop collapses to
public int[] Translate(string word)
{
ArrayList numbers = new ArrayList();
foreach (char character in word)
{
numbers.Add(keys[character]);
}
return (int[]) numbers.ToArray(typeof (int));
}
and the second to
public int[] Translate(string word)
{
return word.Select(c => keys[c]).ToArray();
}