What if the resulting value of a function was used to feed the parameter of the same function? It’s easy enough to call a function, get its result, then pass it back in. However, I wanted to abstract this and make it work for anything whose generic parameter type is the same as the generic result type. If it’s abstracted, there must be some arbitrary end point… right? How about infinity?
I thought about it, then dreamed up a solution involving 3 of our friends we see so often:
1) Func<T, T>
2) Iterator Pattern
3) Extension Method
I then wrote a simple unit test to prove my work.
[TestMethod]
public void Iterate()
{
Func<Int32, Int32> f = x => x + 1;
var nums = f.Iterate(1).Take(10);
Assert.AreEqual(10, nums.Last());
}
There’s a function that adds a number to the parameter. We want to call the Iterate method on the function, passing in the initial value of 1. Then we must take 10 because it will never stop. Given the parameters, the last number should be 10.
Great. With a test available, I went about creating the code.
public static IEnumerable<T> Iterate<T>(this Func<T, T> func, T value)
{
while (true)
{
yield return value;
value = func(value);
}
}
This is going to create a sequence of all the values. Since the initial value may be used (it was in my test), I decided to return it. If you have a graphing function that begins at 0, you would probably want 0 in your list of values. The yield return keyword creates an iterator, and the extension method allows it to appear as though Iterate() was a method that belonged to the function anyway.
After I wrote this code, I called up a friend to explain that I finally got around to creating an infinite iterator. When I told him the function was being called repeatedly, being fed it’s resulting value, he mentioned that I would get a stack overflow as though I were making extremely deep, recursive method calls. I disagreed due to the way it was implemented.
I wrote a test to see if I would have this kind of issue.
[TestMethod]
public void IterateShouldNotStackOverflow()
{
Func<Int32, Int32> f = x => x;
foreach (var n in f.Iterate(1))
{
}
}
I started it up and watched it for a bit. Then I grabbed some coffee, came back, watched it for a bit. Eventually, I grew bored with it. I then set the test to ignore because it is a terrible automated test candidate.
If you run this, remember that it returns an infinite IEnumerable<T>. You should never run an aggregate method on it, and you should never just foreach over it without some termination condition. Iterate() is meant to be used in conjunction with Skip, Take, First.
Here’s the source code to this and other extensions on functions.