The C-Combinator isn't so useless
...and so useless isn't the C-Combinator
Photograph by Andy Morffew, CC BY 2.0, via Wikimedia Commons
Combinators are functional patterns that glue (AKA compose) together functions. These patterns promote single responsibility and decoupling.
The C-Combinator is commonly known as the Flip Combinator. It is formally known as the Cardinal Combinator. The C-Combinator can be defined using lambdas as:
// lambda definition (a -> b -> c)-> b -> a -> c // C# definition Func<T2, Func<T1, T3>> Flip<T1, T2, T3>(Func<T1, Func<T2, T3>> f) => y => x => f(x)(y);
In plain English, it takes a function that accepts two arguments and flips the order of the arguments, such that the second argument is applied first and the first argument is applied second. This can be demonstrated with division:
Func<int, Func<int, float>> Divide = a => b => a / b var flippedDivide = Flip(Divide) // b => a => a / b divide(3)(1); // 3 flippedDivide(3)(1); // 0.333...
Pretty simple right? So what's the use case?
Rearranging arguments for readability
In the last post, we defined the
tee operator using the K-Combinator as such:
Func<T, T> Tee<T>(Action sideEffect) => input => Constant<T>(input)(sideEffect.Invoke(input));
However, the problem is that what we really want is to flip the arguments where the input argument is applied second. Given that the general definition of the K-Combinator is
x => _ => x, a better implementation would be
_ => x => x as it would allow us to define the side effect first before applying the input. We can achieve this by using the C-Combinator:
var Tee = Flip(Constant);
As we can see, the new implementation of
tee is much more concise and arguably much more readable.
Flip allows a developer to describe the functional interface in a way that makes sense from a readability perspective. This avoids being forced into defining a functional interface because of an implementation detail.
The "flip"-side is that developers can also use
Flip to change the functional interface of external dependencies when the functional interface of the dependency doesn't work well with the application implementation.
There have been many occasions where I've wrapped a dependent function with a function just to re-arrange the arguments where a simple
Flip may have sufficed. What other use cases can you think of for this?