Composing methods at runtime Part II

In my last post, I discussed how you could compose methods together at runtime. I hinted that there was more to come.  The issue had to do with the Trace() method.  Here’s sample output from one run of the application, as coded for my last post:

enter operation 
trace 
Printing current sequence 
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
enter operation 
dbl 
enter operation 
done 
2 
4 
6 
8 
10 
12 
14 
16 
18 
20

Notice how the output from Trace happens immediately.  Well, that’s because Trace calls ToList() in order to create a snapshot of the list at that point in time.  But what if you want to defer execution?  Well, you might try the obvious implementation of Trace():

        private
        static IEnumerable<int> Trace(IEnumerable<int> sequence)
{
foreach(int item in sequence)
{
Console.WriteLine(item);
yieldreturn item;
}
}
The doesn’t work.  Because everything is evaluated lazily, including the trace, you’ll get the trace output interspersed with final results:
enter operation 
trace 
enter operation 
dbl 
enter operation 
done 
Printing current sequence 
1 
2 
2 
4 
3 
6 
4 
8 
5 
10 
6 
12 
7 
14 
8 
16 
9 
18 
10 
20

That’s not what I want either.  I want the trace operation to occur as a single operation but not until the entire pipeline is composed, and the entire pipeline gets executed.  What I want is this output:

enter operation 
trace 
enter operation 
dbl 
enter operation 
done 
Printing current sequence 
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
2 
4 
6 
8 
10 
12 
14 
16 
18 
20

Now, I have the trace command execute only when the function composition has completed, and the yet it happens in one operation.  The Trace() method still uses ToList() in order to pull all the elements through the pipeline and get a single trace of the full list. 

To make that work, you have to compose Func objects instead of the sequence.  Then, when you enumerate all the elements in the composed functions, all the composed functions execute in a chain. First, you need a Compose() method that lets you compose two like functions into a single operation that executes the first function, then the second function on all the items in the sequence:

        public
        static
        class Extensions
{
publicstatic Func<IEnumerable<int>> Compose(this Func<IEnumerable<int>> srcFunc,
Func<IEnumerable<int>, IEnumerable<int>> nextFunc)
{
// wish extension syntax worked here.
// () => srcFunc().nextFunc();
return () => nextFunc(srcFunc());
}

// Initial method:
publicstatic Func<IEnumerable<int>> Compose(this IEnumerable<int> src,
Func<IEnumerable<int>, IEnumerable<int>> nextFunc)
{
// wish extension syntax worked here.
// () => src.nextFunc();
return () => nextFunc(src);
}
}
Next, you need to modify the main program logic to compose functions instead of sequences:
 
Func<IEnumerable<int>> sequence = () => Enumerable.Range(1, 10);
bool done = false;
do
{
Console.WriteLine("enter operation");
var input = Console.ReadLine();
switch (input)
{
case"odd":
sequence = sequence.Compose((s) => s.Where(n => n % 2 == 1));
break;
case"even":
sequence = sequence.Compose((s) => s.Where(n => n % 2 == 0));
break;
case"uniq":
sequence = sequence.Compose((s) => s.Distinct());
break;
case"sqr":
sequence = sequence.Compose((s) => s.Select(n => n * n));
break;
case"dbl":
sequence = sequence.Compose((s) => s.Select(n => n * 2));
break;
case"trace":
sequence = sequence.Compose(s => Trace(s));
break;
case"done":
done = true;
break;
}
} while (!done);
foreach (var item in sequence())
Console.WriteLine(item);

All the changes fallout from the fact that sequence is now a Func<IEnumerable<int>> rather than an IEnumerable<int>. Now, all the compositions in the switch statement use that Compose() method to stitch together the functions.  Finally, notice that I need to execute the composed functions in the sequence variable in the final foreach statement.
 
The first version is certainly easier to comprehend.  And, it is simpler. However, it may lack some features that you need, if any of the methods you are composing have any side effects. (It may be important exactly when those side effects occur).
Created: 9/30/2010 7:01:31 PM

Current Projects

I create content for .NET Core. My work appears in the .NET Core documentation site. I'm primarily responsible for the section that will help you learn C#.

All of these projects are Open Source (using the Creative Commons license for content, and the MIT license for code). If you would like to contribute, visit our GitHub Repository. Or, if you have questions, comments, or ideas for improvement, please create an issue for us.

I'm also the president of Humanitarian Toolbox. We build Open Source software that supports Humanitarian Disaster Relief efforts. We'd appreciate any help you can give to our projects. Look at our GitHub home page to see a list of our current projects. See what interests you, and dive in.

Or, if you have a group of volunteers, talk to us about hosting a codeathon event.