I found it helpful to add these extension methods on while doing Functional Programming in C#.
IEnumerable
Func
Predicate
IDictionary
IEnumerable Extensions
Lets start with IEnumerable extension methods. But before that, below are the usage examples of each of these extension APIs:
Doing Side-effect on each value (single-valued or a 2-element-tupled enumerable)
// Single-Valued
string [] names = { "Brahma", "Vishnu", "Mahesh" };
names
.Select(name => name.ToUpper())
.ForEach(Console.WriteLine);
// BRAHMA
// VISHNU
// MAHESH
//2-element-tuple
IList alphabets = new List { 'a', 'b' };
IList numbers = new List { 1, 2 };
IList combinations = new List();
foreach (var alphabet in alphabets) {
foreach (var number in numbers) {
combinations.Add((alphabet, number));
}
}
combinations
.ForEach((a, n) => Console.WriteLine($"({a}, {n})"));
// (a, 1)
// (a, 2)
// (b, 1)
// (b, 2)
Peeking each value
var result = "all mimsy were the borogoves and the momeraths"
.Split(" ")
.Peek(word => Console.WriteLine($"Where({word})"))
.Where(word => word.Length < 4)
.Peek(word => Console.WriteLine($"Select({word})"))
.Select(word => word.ToUpper())
.Aggregate("", (acc, elem) => acc + " " + elem)
.Trim();
// Where(all)
// Select(all)
// Where(mimsy)
// Where(were)
// Where(the)
// Select(the)
// Where(borogoves)
// Where(and)
// Select(and)
// Where(the)
// Select(the)
// Where(momeraths)
Console.WriteLine(result); // ALL THE AND THE
Producing infinite values using Iterate and Generate
// Using Iterate
bool IsPrime(int number) {
return IEnumerableExtensions.Iterate(2, x => x + 1)
.Take(number)
.Where(n => n number % n != 0);
}
Console.WriteLine(IsPrime(2)); // True
Console.WriteLine(IsPrime(3)); // True
Console.WriteLine(IsPrime(4)); // False
// Using Generate
string Captcha(int howMany) {
var random = new Random();
return IEnumerableExtensions.Generate(() => random.Next(65, 123))
.Where(integer => Char.IsLetter((char)integer))
.Select(integer => (char)integer)
.Take(howMany)
.Aggregate(new StringBuilder(), (acc, elem) => acc.Append(elem))
.ToString();
}
Console.WriteLine(Captcha(4)); //RVkn, Ccmo etc... each run produces different output.
Scanning reduction
// Using Scan to print Running Sum
new List { 1, 2, 3, 4 }
.Scan(0, (acc, elem) => acc + elem)
.ForEach(Console.WriteLine);
// 1
// 3
// 6
// 10
De-constructing an IEnumerable
var (first, second, third, rest) = new List { 1, 2, 3, 4 };
Console.WriteLine($"First = {first}"); // 1
Console.WriteLine($"Second = {second}"); // 2
Console.WriteLine($"Third = {third}"); // 3
Console.WriteLine($"Rest = {rest}"); // IEnumerable of remaining elements.
Maximum and Minimum By a key
var people = new [] {
new { Name = "Eina", Age = 10 },
new { Name = "Mina", Age = 15 },
new { Name = "Dika", Age = 19 },
new { Name = "Tika", Age = 10 },
new { Name = "Maka", Age = 28 },
new { Name = "Naka", Age = 35 },
new { Name = "Saka", Age = 28 },
new { Name = "Taka", Age = 45 }
}.ToList();
Console.WriteLine("*** Eldest person ***");
Console.WriteLine(people.MaxBy(p => p.Age)); // { Name = Taka, Age = 45 }
Console.WriteLine("*** Youngest person ***");
Console.WriteLine(people.MinBy(p => p.Age)); // { Name = Eina, Age = 10 }
Finally, here is the IEnumerable extension class which implements all the above APIs.
static class IEnumerableExtensions {
// Doing Side-Effects
// Single-Valued
public static void ForEach<T>(this IEnumerable<T> @this, Action<T> action) {
if (@this == null)
throw new ArgumentNullException("Require non-null source!");
if (action == null)
throw new ArgumentNullException("Require non-null action!");
foreach (T item in @this) {
action(item);
}
}
// 2-element-tuple enumerable
public static void ForEach<T1, T2>(this IEnumerable<(T1, T2)> @this, Action<T1, T2> action) {
if (@this == null)
throw new ArgumentNullException("Require non-null source!");
if (action == null)
throw new ArgumentNullException("Require non-null action!");
foreach (var item in @this) {
action(item.Item1, item.Item2);
}
}
// Peek (but don't do side-effect)
public static IEnumerable<T> Peek<T>(this IEnumerable<T> @this, Action<T> action) {
IEnumerable<T> PeekedEnumerable() {
foreach (T item in @this) {
action(item);
yield return item;
}
}
if (@this == null)
throw new ArgumentNullException("Require non-null list!");
if (action == null)
throw new ArgumentNullException("Require non-null action!");
return PeekedEnumerable();
}
// Produce Infinite values using functions.
public static IEnumerable<T> Iterate<T>(T inital, Func<T, T> next) {
var nextValue = inital;
while(true) {
yield return nextValue;
nextValue = next(nextValue);
}
}
public static IEnumerable<T> Generate<T>(Func<T> generator) {
while(true) {
yield return generator();
}
}
// Deconstruction
public static void Deconstruct<T>(this IEnumerable<T> @this,
out T first, out IEnumerable<T> rest) {
if (@this == null) {
throw new ArgumentException("Collection cannot be null!");
}
first = @this.First();
rest = @this.Skip(1);
}
public static void Deconstruct<T>(this IEnumerable<T> @this,
out T first, out T second, out IEnumerable<T> rest) =>
(first, (second, rest)) = @this;
public static void Deconstruct<T>(this IEnumerable<T> @this,
out T first, out T second, out T third, out IEnumerable<T> rest) =>
(first, second, (third, rest)) = @this;
// Scanned Reduction
public static IEnumerable<U> Scan<T, U>(this IEnumerable<T> @this, U identity, Func<U, T, U> func) {
IEnumerable<U> ScannedEnumerable() {
var acc = identity;
foreach (var item in @this) {
acc = func(acc, item);
yield return acc;
}
}
return ScannedEnumerable();
}
// Minimum and Maximum by a key
public static T MinBy<T, Key>(this IEnumerable<T> @this, Func<T, Key> selector) {
if (@this == null)
throw new ArgumentNullException("Require non-null list!");
if (selector == null)
throw new ArgumentNullException("Require non-null selector!");
var minimum = @this.First();
var minKey = selector(minimum);
var comparer = Comparer<Key>.Default;
foreach (var item in @this) {
var currentKey = selector(item);
if (comparer.Compare(currentKey, minKey) < 0) {
minKey = currentKey;
minimum = item;
}
}
return minimum;
}
public static T MaxBy<T, Key>(this IEnumerable<T> @this, Func<T, Key> selector) {
if (@this == null)
throw new ArgumentNullException("Require non-null list!");
if (selector == null)
throw new ArgumentNullException("Require non-null selector!");
var maximum = @this.First();
var maxKey = selector(maximum);
var comparer = Comparer<Key>.Default;
foreach (var item in @this) {
var currentKey = selector(item);
if (comparer.Compare(currentKey, maxKey) > 0) {
maxKey = currentKey;
maximum = item;
}
}
return maximum;
}
}
Function Extensions
Here are the examples of Function Composition and Currying.
Func<int, int> Square = x => x*x;
int Multiply(int x, int y) => x * y;
var uncurriedMultiply = new Func<int, int, int>(Multiply);
var multiplyCurried = uncurriedMultiply.Curry();
Console.Out.WriteLine($"multiplyCurried(2)(3) = {multiplyCurried(2)(3)}"); // 6
var uncurry = multiplyCurried.Uncurry();
Console.Out.WriteLine($"uncurry(2, 3) = {uncurry(2, 3)}"); // 6
// Express Twice in terms of multiplyCurried
int Twice(int x) => multiplyCurried(2)(x);
// Express Triple in terms of multiplyCurried
var thrice = multiplyCurried(3);
Func<int, int> Triple = x => thrice(x);
var st = Square.AndThen(Triple);
Console.Out.WriteLine($"st(2) = {st(2)}"); // 12
var ts = Square.Compose(Triple);
Console.Out.WriteLine($"ts(2) = {ts(2)}"); // 36
var twice = new Func<int, int>(Twice);
var ds = twice.AndThen(Square);
Console.Out.WriteLine($"ds(2) = {ds(2)}"); // 16
Now, lets look at the definitions of Function extension methods. All of them are Higher-Order functions.
public static class FuncExtensions {
// Compose functions using AndThen and Compose
public static Func<A, C> AndThen<A, B, C>(this Func<A, B> f1, Func<B, C> f2) =>
a => f2(f1(a));
public static Func<A, C> Compose<A, B, C>(this Func<B, C> f1, Func<A, B> f2) =>
a => f1(f2(a));
// Curry and Uncurry for 2-arg function
public static Func<A, Func<B, C>> Curry<A, B, C>(this Func<A, B, C> f) =>
a => b => f(a, b);
public static Func<A, B, C> Uncurry<A, B, C>(this Func<A, Func<B, C>> f) =>
(a, b) => f(a)(b);
}
Predicate Extensions
Next, lets look at Predicate extension methods. These are on similar lines to the ones found in Java. Again, all of them are Higher-Order functions.
public static class PredicateExtensions {
public static Predicate<T> Not<T>(this Predicate<T> @this) {
if (@this == null)
throw new ArgumentNullException("Require non-null Predicate!");
return t => !@this(t);
}
public static Predicate<T> And<T>(this Predicate<T> @this, Predicate<T> that) {
if (@this == null || that == null)
throw new ArgumentNullException("Require non-null Predicate!");
return t => @this(t) && that(t);
}
public static Predicate<T> Or<T>(this Predicate<T> @this, Predicate<T> that) {
if (@this == null || that == null)
throw new ArgumentNullException("Require non-null Predicate!");
return t => @this(t) || that(t);
}
}
Dictionary Extensions
Next, lets look at IDictionary extension methods. It allows you to return a specified default value for a non-existent key in the dictionary. The two forms are:
- Return a default value
- Return a supplier function which then returns a default value
In case you don’t have a return value, the third form GetValueOrThrow
allows you to throw an exception. Again, these are on similar lines to the ones found in Java and Scala.
public static class IDictionaryExtensions {
public static V GetValueOrDefault<K, V>(this IDictionary<K, V> @this, K key, V defaultValue) {
if (@this == null)
throw new ArgumentNullException("Require non-null Dictionary!");
if (@this.ContainsKey(key))
return @this[key];
return defaultValue;
}
public static V GetValueOrDefault<K, V>(this IDictionary<K, V> @this, K key, Func<V> defaultValueSupplier) {
if (@this == null)
throw new ArgumentNullException("Require non-null Dictionary!");
if (@this.ContainsKey(key))
return @this[key];
return defaultValueSupplier();
}
public static V GetValueOrThrow<K, V, E>(this IDictionary<K, V> @this, K key, E exception)
where E : Exception {
if (@this == null)
throw new ArgumentNullException("Require non-null Dictionary!");
if (@this.ContainsKey(key))
return @this[key];
throw exception;
}
}
Hope you find these useful in your day-to-day work!
Like this:
Like Loading...