-
-
Notifications
You must be signed in to change notification settings - Fork 422
Thinking Functionally: Function composition
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
.
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 theBackCompose
extension methods can be used to flip the composition of the functions.
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);