Thinking Functionally: Function composition

Paul Louth edited this page May 17, 2018 · 11 revisions

We've mentioned function composition a number of times in passing now, but what does it actually mean? It can seem quite intimidating at first, but it is actually quite simple.

Say that you have a function f that maps from type T1 to type T2, and say that you also have a function g that maps from type T2 to type T3. Then you can connect the output of f to the input of g, creating a new function that maps from type T1 to type T3.

Functions Composition

Here's an example:

static float f(int x)  => x * 3.0f;  // f is int->float
static bool g(float x) => x > 4.0f;  // g is float->bool

We can create a new function h that takes the output of f and uses it as the input for g.

static bool h(int x)
{
   var y = f(x);
   return g(y);
}

A much more compact way is this:

static bool h(int x) => g(f(x));    // h is int->bool

//test
var x = h(1);   // x == false
var y = h(2);   // y == true

So far, so straightforward. What is interesting is that we can define a new function called compose that, given functions f and g, combines them in this way without even knowing their signatures.

static Func<A, C> compose<A, B, C>(Func<A, B> a, Func<B, C> b) =>
    v => b(a(v));

compose is part of LanguageExt.Prelude and can be used to compose up to 7 functions.

Now we compose f and g from before into a combined function of h:

Func<int, bool> h = compose(f, g);

//test
var x = h(1);   // x == false
var y = h(2);   // y == true

You can also call the Compose extension method on Func<A, B> if you prefer the more fluent style.

Func<int, bool> h = f.Compose(g);

//test
var x = h(1);   // x == false
var y = h(2);   // y == true

The backCompose Prelude function and the BackCompose extension methods can be used to flip the composition of the functions.

Limitations

Because of a limitation in how C# treats 'method groups', you will have to provide the generic arguments if you're working with static methods:

var h = compose<int, float, bool>(f, g);

One way around that is to declare your static methods as readonly static fields:

static readonly Func<int, float> f = 
    x => x * 3.0f;

static readonly Func<float, bool> g = 
    x => x > 4.0f;

This is a really frustrating limitation of C# and has been raised as an issue. The readonly static field approach isn't too bad, although there may be performance considerations in regular use. It does however make all uses of the first-class functions very easy to parse:

This

var h = compose(f, g);

Is clearer than this:

var h = compose<int, float, bool>(f, g);

NEXT: Combinators

You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.