Ted Neward goes through a code kata with several programming paradigms in the latest Working Programmer column. Being a coding polyglot is all the rage nowadays, and understanding multiple paradigms is essential to being a well-rounded developer. Ted goes through procedural style, modifies it to be object-oriented, demonstrates how to use meta and then dynamic programming. Finally, he makes it more functional. The proposed solution adds an aspect of functional programming, but it can be taken further.
First, let me post Ted’s functional solution to the Data Munging kata.
TextParser<WeatherData> parser = new TextParser<WeatherData>();
parser.LineParseVerifier =
(line) =>
{
if ( (line.Trim().Length == 0) ||
(line.Contains("MMU")) ||
(line.Contains("Dy")) ||
(line.Contains("mo")))
return false;
else
return true;
};
parser.ColumnExtracter =
(line) =>
{
WeatherData wd = new WeatherData();
wd.Day = line.Substring(0, 4);
wd.MxT = line.Substring(5, 6);
wd.MnT = line.Substring(12, 6);
return wd;
};
List<WeatherData> results = parser.Parse("weather.dat");
This is actually a hybrid object-oriented/functional solution. TextParser<T> is a functional version of the template method pattern. It replaces the abstract methods with delegates, likely using the standardized Func classes. LineParseVerifier would be Func<string, bool>, and ColumnExtracter would be Func<string, T>. Instead of creating a subclass, you only need to define the delegates that are called. The power of this approach is that you have a reusable class without class explosion.
This solution has been available since C# 2.0. It’s made cleaner in C# 3.0 with the introduction of the Func and Action classes and lambda expressions. The 2.0 version would have used defined delegates and anonymous methods.
C# is an object-oriented language, but C# 3.0 introduced other functional aspects than the ones used by this solution. These are extension methods and list comprehension via LINQ. With these, you can approach the problem in an entirely different manner.
var weatherInfo = from line in File.ReadLines("weather.data")
where line.Trim().Length > 0
&& !line.Contains("MMU")
&& !line.Contains("Dy")
&& !line.Contains("mo")
select new
{
Day = Int32.Parse(line.Substring(0, 4).Replace("*", " ")),
MxT = Single.Parse(line.Substring(5, 6).Replace("*", " ")),
MnT = Single.Parse(line.Substring(12, 6).Replace("*", " "))
};
Console.WriteLine("Max spread: " + weatherInfo.Max(wd => wd.MxT - wd.MnT));
With this declarative approach, there’s no need to create the TextParser or WeatherData classes. The functions are defined in the where and select statements. You can use the extension methods of LINQ directly, and extract them to make the code even more readable and declarative. The downside is that classes must be created, but they are reusable.
The classes:
public class WeatherData
{
public int Day { get; set; }
public float MxT { get; set; }
public float MnT { get; set; }
}
public static class EnumerableStringExtensions
{
public static IEnumerable<string> WhereWeatherData(this IEnumerable<string> strings)
{
return strings.Where(line => line.Trim().Length > 0
&& !line.Contains("MMU")
&& !line.Contains("Dy")
&& !line.Contains("mo"));
}
public static IEnumerable<WeatherData> MapWeatherData(this IEnumerable<string> strings)
{
return strings.Select(line => new WeatherData
{
Day = Int32.Parse(line.Substring(0, 4).Replace("*", " ")),
MxT = Single.Parse(line.Substring(5, 6).Replace("*", " ")),
MnT = Single.Parse(line.Substring(12, 6).Replace("*", " "))
});
}
}
And the call:
var weatherInfo = File.ReadLines("weather.data")
.WhereWeatherData()
.MapWeatherData();
You can approach this a several different ways in a functional language. The above would read something like: let weatherInfo = mapWeatherData whereWeatherData File.ReadAllLines(“weather.data”);
There are many different approaches to a problem, but some are better than others depending on what you’re trying to accomplish. It is perfectly acceptable to use the procedural approach if it’s a one shot program. Use classes when you need to encapsulate functionality for reusability. I tend to use extension methods when the functionality belongs to another object, or to improve the readability of my code. However, the approach I demonstrated may not be ideal for the application you’re developing. Therefore, it is important to know the options available to give yourself more flexibility when coding.
**EDIT** Jonathan Allen pointed out that ReadLines is the correct method when iterating the lines in the deferred manner used in the examples. The original was using ReadAllLines which does not use deferred execution.