using System; using System.Linq; using Example; namespace Example { public class Functional { public static void IEshow ( IEnumerable<int> v ) { Console.Write ( "Values:" ); foreach ( int x in v ) { Console.Write ( $" {x}" ); } Console.WriteLine ( "" ); } public static IEnumerable<int> map ( Func<int, int> lfunc, int [] values ) { return values.Select ( lfunc ); } public static IEnumerable<int> filter ( Func<int, bool> lfunc, int [] values ) { return values.Where ( lfunc ); } } } namespace HogHeaven { public class Program { public static void Main(string[] args) { int [] m_values = [7,2,3,4,5,6]; int [] f_values = [1,2,3,4,5,6,7,8,9]; IEnumerable<int> xx; xx = Functional.map ( x => x*x, m_values ); Functional.IEshow ( xx ); xx = Functional.filter ( x => (x % 2) == 0, f_values ); Functional.IEshow ( xx ); } } } // THE ENDThe crux of the issue here is the type designation for the first argument of the map and filter functions. The odd business of "Func<int, bool>" is what C# calls a delegate type, and it seems to have a whole raft of them. The first thing in this generic (here "int") is the type of the argument, the last thing (here "bool") is the type of the return value. Once you understand that this is the game to play, the rest is easy.
I should say something about the IEnumerable
These "map" and "filter" are just wrappers on LINQ "Select" and "Where" and the idiomatic
C# programmer would want to use the latter. Making this connection is useful to me,
coming from a Haskell background -- it helps me to understand one aspect of LINQ.
An additional by-product of all this has been learning about lambda expressions,
and in particular (in my case) how to use them as arguments to my own functions.
Apparently "lambda expression" is the proper language, though I probably call them
lambda functions just as often, perhaps improperly.
Just for the record, the calls in Main() can be condensed
(once we rename IEshow() to show() ) down to:
In Haskell, to combine map and filter, we would have to chain the two functions together.
Or we could get fancy and do "function composition", creating a new function that would do both the
filtering and mapping. Operating on functions is what functional languages are all about
after all. Functions that operate on functions are "higher order functions" in the functional
world -- but now we are drifting away from C#, or maybe we are ....
show ( map ( x => x*x, [7,2,3,4,5,6] ) );
show ( filter ( x => (x % 2) == 0, [1,2,3,4,5,6,7,8,9] ) );
Combinations
LINQ allows us to Select and Where at the same time (like most database query language do).
So we can construct a statement like:
IEnumerable
Here the select does no mapping, it just says, "give me the whole record" - but it could perform
a mapping function or it could select fields from the data or whatever.
This delegate thing
Good old C allowed you to pass a function pointer around. But this was C and all that was getting passed
was the hardware address of the function. It was up to you (and maybe with some help from the compiler)
to keep straight what the arguments and return type were. And being C, you were free to force it to do any
crazy thing -- a feature I enjoyed, exploited, and appreciated. But in the C# world, when you start passing
methods around, you have to give them a proper type, and the type indicates all the types for the method
both coming and going, hence Func<int, bool> in one of our cases.
These are the "delegate" types (at least that is what I am calling them at this point in time).
There are many other such types and they don't all begin with "Func" so there is more to be learned.
Feedback? Questions?
Drop me a line!