[LINQ via C# series]
[C# functional programming in-depth series]
Latest version: https://weblogs.asp.net/dixin/functional-csharp-function-composition-and-method-chaining
In object-oriented programming, objects can be composed to build more complex object. Similarly, in functional programming. functions can be composed to build more complex function.
Forward and backward composition
It is very common to pass a function’s output to another function as input:
internal static void OutputAsInput(){ string input = "-2.0"; int output1 = int.Parse(input); // string -> int int output2 = Math.Abs(output1); // int -> int double output3 = Convert.ToDouble(output2); // int -> double double output4 = Math.Sqrt(output3); // double -> double}So above Abs function and Sqrt function can be combined:
// string -> doubleinternal static double Composition(string input) => Math.Sqrt(Convert.ToDouble(Math.Abs(int.Parse(input))));The above function is the composition of int.Parse, Math.Abs Convert.ToDouble, and Math.Sqrt. Its return value is the last function Math.Sqrt’s return value. Generally, a forward composition operator and a backward composition operator can be defined as extension method:
public static partial class FuncExtensions{ public static Func<T, TResult2> After<T, TResult1, TResult2>( this Func<TResult1, TResult2> function2, Func<T, TResult1> function1) => value => function2(function1(value));
public static Func<T, TResult2> Then<T, TResult1, TResult2>( // Before. this Func<T, TResult1> function1, Func<TResult1, TResult2> function2) => value => function2(function1(value));}The above functions can be composed by calling either After or Then:
internal static void Compose(){ Func<string, int> parse = int.Parse; // string -> int Func<int, int> abs = Math.Abs; // int -> int Func<int, double> convert = Convert.ToDouble; // int -> double Func<double, double> sqrt = Math.Sqrt; // double -> double
// string -> double Func<string, double> composition1 = sqrt.After(convert).After(abs).After(parse); composition1("-2.0").WriteLine(); // 1.4142135623731
// string -> double Func<string, double> composition2 = parse.Then(abs).Then(convert).Then(sqrt); composition2("-2.0").WriteLine(); // 1.4142135623731}The LINQ query methods, like Where, Skip, Take, cannot be directly composed like this:
namespace System.Linq{ public static class Enumerable { // (IEnumerable<TSource>, TSource -> bool) -> IEnumerable<TSource> public static IEnumerable<TSource> Where<TSource>( this IEnumerable<TSource> source, Func<TSource, bool> predicate);
// (IEnumerable<TSource>, int) -> IEnumerable<TSource> public static IEnumerable<TSource> Skip<TSource>( this IEnumerable<TSource> source, int count);
// (IEnumerable<TSource>, int) -> IEnumerable<TSource> public static IEnumerable<TSource> Take<TSource>( this IEnumerable<TSource> source, int count);
// Other members. }}They all return IEnumerable
// Func<TSource, bool> -> IEnumerable<TSource> -> IEnumerable<TSource>internal static Func<IEnumerable<TSource>, IEnumerable<TSource>> Where<TSource>( Func<TSource, bool> predicate) => source => Enumerable.Where(source, predicate);
// int -> IEnumerable<TSource> -> IEnumerable<TSource>internal static Func<IEnumerable<TSource>, IEnumerable<TSource>> Skip<TSource>( int count) => source => Enumerable.Skip(source, count);
// int -> IEnumerable<TSource> -> IEnumerable<TSource>internal static Func<IEnumerable<TSource>, IEnumerable<TSource>> Take<TSource>( int count) => source => Enumerable.Take(source, count);They are curried from the original query methods, with the first parameter and second parameter swapped. After being called with a argument, they return IEnumerable
internal static void LinqWithPartialApplication(){ // IEnumerable<TSource> -> IEnumerable<TSource> Func<IEnumerable<int>, IEnumerable<int>> where = Where<int>(int32 => int32 > 0); Func<IEnumerable<int>, IEnumerable<int>> skip = Skip<int>(1); Func<IEnumerable<int>, IEnumerable<int>> take = Take<int>(2);
IEnumerable<int> query = take(skip(where(new int[] { 4, 3, 2, 1, 0, -1 }))); foreach (int result in query) // Execute query. { result.WriteLine(); }}So these LINQ query methods can be composed through the curried helper functions:
internal static void ComposeLinqWithPartialApplication(){ Func<IEnumerable<int>, IEnumerable<int>> composition = Where<int>(int32 => int32 > 0) .Then(Skip<int>(1)) .Then(Take<int>(2));
IEnumerable<int> query = composition(new int[] { 4, 3, 2, 1, 0, -1 }); foreach (int result in query) // Execute query. { result.WriteLine(); }}Forward pipeline
The forward pipe operator, which forwards argument to call function, can also help function composition. It can also be defined as extension method:
public static partial class FuncExtensions{ public static TResult Forward<T, TResult>(this T value, Func<T, TResult> function) => function(value);}
public static partial class ActionExtensions{ public static void Forward<T>(this T value, Action<T> function) => function(value);}The following example demonstrates how to use it:
internal static void Forward(){ "-2" .Forward(int.Parse) // string -> int .Forward(Math.Abs) // int -> int .Forward(Convert.ToDouble) // int -> double .Forward(Math.Sqrt) // double -> double .Forward(Console.WriteLine); // double -> void
// Equivalent to: Console.WriteLine(Math.Sqrt(Convert.ToDouble(Math.Abs(int.Parse("-2")))));}The Forward extension method can be useful with the null conditional operator to simplify the code, for example:
internal static void ForwardAndNullConditional(IDictionary<string, object> dictionary, string key){ object value = dictionary[key]; DateTime? dateTime1; if (value != null) { dateTime1 = Convert.ToDateTime(value); } else { dateTime1 = null; }
// Equivalent to: DateTime? dateTime2 = dictionary[key]?.Forward(Convert.ToDateTime);}This operator can alkso help composing LINQ query methods:
internal static void ForwardLinqWithPartialApplication(){ IEnumerable<int> source = new int[] { 4, 3, 2, 1, 0, -1 }; IEnumerable<int> query = source .Forward(Where<int>(int32 => int32 > 0)) .Forward(Skip<int>(1)) .Forward(Take<int>(2)); foreach (int result in query) // Execute query. { result.WriteLine(); }}Fluent method chaining
In contrast of static method, instance methods can be easily composed by just chaining the calls, for example:
internal static void InstanceMethodChaining(string @string){ string result = @string.TrimStart().Substring(1, 10).Replace("a", "b").ToUpperInvariant();}The above functions are fluently composed because each of them returns an instance of that type, so that another instance method can be called fluently. Unfortunately, many APIs are not designed following this pattern. Take List
namespace System.Collections.Generic{ public class List<T> : IList<T>, IList, IReadOnlyList<T> { public void Add(T item);
public void Clear();
public void ForEach(Action<T> action);
public void Insert(int index, T item);
public void RemoveAt(int index);
public void Reverse();
// Other members. }}These methods return void, so they cannot be composed by chaining. These existing APIs cannot be changed, but the extension method syntactic sugar enables virtually adding new methods to an existing type. So fluent methods can be “added” to List
public static class ListExtensions{ public static List<T> FluentAdd<T>(this List<T> list, T item) { list.Add(item); return list; }
public static List<T> FluentClear<T>(this List<T> list) { list.Clear(); return list; }
public static List<T> FluentForEach<T>(this List<T> list, Action<T> action) { list.ForEach(action); return list; }
public static List<T> FluentInsert<T>(this List<T> list, int index, T item) { list.Insert(index, item); return list; }
public static List<T> FluentRemoveAt<T>(this List<T> list, int index) { list.RemoveAt(index); return list; }
public static List<T> FluentReverse<T>(this List<T> list) { list.Reverse(); return list; }}By always returning the first parameter, these extension methods can be composed by fluent chaining, as if they are instance methods:
internal static void ListFluentExtensions(){ List<int> list = new List<int>() { 1, 2, 3, 4, 5 } .FluentAdd(1) .FluentInsert(0, 0) .FluentRemoveAt(1) .FluentReverse() .FluentForEach(value => value.WriteLine()) .FluentClear();}As fore mentioned, these extension method calls are compiled to normal static method calls:
public static void CompiledListExtensions(){ List<int> list = ListExtensions.FluentClear( ListExtensions.FluentForEach( ListExtensions.FluentReverse( ListExtensions.FluentRemoveAt( ListExtensions.FluentInsert( ListExtensions.FluentAdd( new List<int>() { 1, 2, 3, 4, 5 }, 1), 0, 0), 1) ), value => value).WriteLine() );}LINQ query methods composition
In C#, LINQ query methods are composed better with this fluent method chaining approach. IEnumerable
namespace System.Collections{ public interface IEnumerable { IEnumerator GetEnumerator(); }}
namespace System.Collections.Generic{ public interface IEnumerable<out T> : IEnumerable { IEnumerator<T> GetEnumerator(); }}When .NET Framework 3.5 introduces LINQ, IEnumerable
The fore mentioned OrderBy method is slightly different. It accepts IEnumerable
namespace System.Linq{ public interface IOrderedEnumerable<TElement> : IEnumerable<TElement>, IEnumerable { IOrderedEnumerable<TElement> CreateOrderedEnumerable<TKey>( Func<TElement, TKey> keySelector, IComparer<TKey> comparer, bool descending); }
public static class Enumerable { public static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector);
public static IOrderedEnumerable<TSource> OrderByDescending<TSource, TKey>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector);
public static IOrderedEnumerable<TSource> ThenBy<TSource, TKey>( this IOrderedEnumerable<TSource> source, Func<TSource, TKey> keySelector);
public static IOrderedEnumerable<TSource> ThenByDescending<TSource, TKey>( this IOrderedEnumerable<TSource> source, Func<TSource, TKey> keySelector); }}IOrderedEnumerable
There are also a few methods returning a single value instead of IEnumerable
public static class Enumerable{ public static TSource First<TSource>(this IEnumerable<TSource> source);
public static TSource Last<TSource>(this IEnumerable<TSource> source);}Usually they terminate the LINQ query, since other query methods cannot be composed after these methods, unless the returned single value is still a IEnumerable
There are other parities of LINQ to Objects query represented by IEnumerable
namespace System.Linq{ public static class ParallelEnumerable { public static ParallelQuery<TSource> Where<TSource>( this ParallelQuery<TSource> source, Func<TSource, bool> predicate);
public static OrderedParallelQuery<TSource> OrderBy<TSource, TKey>( this ParallelQuery<TSource> source, Func<TSource, TKey> keySelector);
public static ParallelQuery<TResult> Select<TSource, TResult>( this ParallelQuery<TSource> source, Func<TSource, TResult> selector);
// Other members. }
public static class Queryable { public static IQueryable<TSource> Where<TSource>( this IQueryable<TSource> source, Func<TSource, bool> predicate);
public static IOrderedQueryable<TSource> OrderBy<TSource, TKey>( this IQueryable<TSource> source, Func<TSource, TKey> keySelector);
public static IQueryable<TResult> Select<TSource, TResult>( this IQueryable<TSource> source, Func<TSource, TResult> selector);
// Other members. }}The details of IEnumerable