The Pipe-Forward operator in C# (or an intro to Combinators)

Photograph by Yathin S Krishnappa, CC BY-SA 3.0, via Wikimedia Commons

The Pipe-Forward operator

The pipe-forward operator is a function that allows for composition of functions. If you've used DOS or *nix, this is the equivalent of the | operator, where the result of one command is passed into the next. In the case of the pipe-forward operator, it passes the result of one function to the next function as an input.

The pipe operator is useful for creating type-safe pipelines of transformation. These allow for easier refactoring and readability as the operations occur from left to right. F# and Elixir have this operator and it's represented by |>. The following code will all return the same result:

var a = f(x);
var b = g(a);
var c = h(b);
var result1 = i(c);

var result2 = i(h(g(f(x))));

var result3 = x |> f |> g |> h |> i;

The pipe operator feels more natural to read using the English standard of left to right. Unfortunately, in C#, we currently cannot add the |> operator. It is under discussion and hopefully will be added in the future.

C# Implementation

For now, the simplest implementation can be achieved through the following extension:

public static class PipeExtension {
  public static TOutput Pipe<TInput, TOutput>(this TInput input, in Func<TInput, TOutput> map)
    where TInput : class
    where TOutput : class =>
      map(input);
}

Using the above example, we can now build a pipeline in C# as follows:

x
  .Pipe(f)
  .Pipe(g)
  .Pipe(h)
  .Pipe(i);

Nice! Future changes to this pipeline will ensure that developers can change operations while considering the upstream and downstream effects.

The Thrush Combinator

If you haven't seen this pattern before, this extension is an implementation of the function more formally known as the Thrush Combinator.

Combinators are patterns from Combinatory logic ... Wait! Wait! Wait! Don't go away, this is really cool stuff! The Combinator patterns are extremely useful for gluing together functions while making the guarantee that the final function will operate based on the rules of that combinator. For me, this allows me to decouple how business rules are connected from what the business rules are.

Given a value x and a function f, the Thrush Combinator can be described as follows:

x => f => f(x)

This maps directly to our implementation of the Pipe extension. As a developer, I can focus on my implementation of the business rule f and the data inputs x, but allow the Combinator to take care of gluing these things together. This makes it easier to test business rules as small pieces or as a whole. Things get a lot more interesting when functional data structures are added into the mix. (I resisted to say the M-word.)

Hopefully, this has provided a small taste for Combinators. I plan on writing more about the pragmatic use of Combinators in the future. If you can't wait, then you can follow my experiments on GitHub .

No Comments Yet