Mr Rampage
The Rampage

Follow

The Rampage

Follow

The C-Combinator isn't so useless

...and so useless isn't the C-Combinator

Mr Rampage's photo
Mr Rampage
·Mar 16, 2021·

3 min read

The C-Combinator isn't so useless

Photograph by Andy Morffew, CC BY 2.0, via Wikimedia Commons

The C-Combinator

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?

 
Share this