Expand - 7 higher order functions
This is my fifth post in a series about higher order functions and I'm starting to see the end of it. This one was the hardest to write, because I didn't have any good material or practical experience, but I think that it went decent anyway.
Expand
My previous post described Reduce, a function to aggregate values in a list. Expand is the opposite of reduce, where you take a value, and expand it onto a list. Here's the function signature.
IEnumerable<T> Expand<T>(Func<T, T> yield_fn, Predicate<T> while_fn, T init)
It takes a yield function, for yielding new values in the list. It takes a predicate function that determines when the result list stops generate. If set to always return true, the IEnumerable will be infinite. That could be useful when taken advantage of or devestating for the careless. The init argument is where the calculation starts.
Examples
Here the obvious butt stupid example.
Expands a list from 1 up to infinity. I'm not sure what uses that have, except debugging.
Talking of useless applications. Fibonacci is an obvious number series when you start from a base value and want to expand it. This uses a state variable previous
outside the lambda.
Let's say we have a list of employees and they all have salaries. We would like a table of employee salaries over 3 years, calculated on a 3% raise.
const double Raise = 1.03;
var table = employees.Map(e => new { e.Name, Salaries = e.Salary.Expand(amount => amount * Raise) });
// Print var format = "{0,14} | {1,7} | {2,7} | {3,7}"; Console.WriteLine(format, string.Empty, "Year 1", "Year 2", "Year 3"); foreach (var line in table) { var salaries = line.Salaries.Take(3).ToArray(); Console.WriteLine(format, line.Name, salaries[0], salaries[1], salaries[2]); } // | Year 1 | Year 2 | Year 3 // John Smith | 45000 | 46350 | 47740,5 // Carla Sewer | 38000 | 39140 | 40314,2 // Sam Shipwright | 55000 | 56650 | 58349,5
The yield functionality of both map and expand plays nicely here, making sure that no values are calculated until we request them.
Implementation
The implementation is pretty straight forward. I use an overload to provide an extension method that does gives infinite lists as default value to while_fn.
if (while_fn == null)
{
throw new ArgumentNullException("while_fn", "Supplied while_fn argument to Expand is not allowed to be null");
}
var current = init;
yield return current; // yield initial value
while (while_fn(current))
{
// yield each calculated new value
yield return current = yield_fn(current);
}
}
public static IEnumerable<T> Expand<T>(this T init, Func<T, T> yieldfn, Predicate<T> whilefn) { return Expand(yieldfn, whilefn, init); }
public static IEnumerable<T> Expand<T>(this T init, Func<T, T> yieldfn) { return Expand(yieldfn, i => true, init); }
Conclusion
Expand is perfect when you need to generate data on demand. The beautiful thing is that each new value is lazily evaluated, meaning you will only calculate as much as needed, and expanded result may be performance expensive. We could get a directory from the filesystem, and expand this directories subdirectories or files. They wouldn't be requested until enumerated upon.
I see some great functionality originating from my understanding of Expand, and I hope I will remember to use it next time there is an opportunity.