Functional programming for the OO developer (Part 3)

in #programming7 years ago (edited)

Now you have wrapped your head around the limitation on functions we imposed in Part 2 it is time to build back to a place where functions are useful again.

Compose all the things

Function composition is nothing special. It simply means that given the following:

Func<int, double> someMaths = value => ...
Func<double, string> formatResult = value => ...

It stands to reason that we can take the output of someMaths and feed it directly into the input of formatResult. We can glue them together to create a new function:

// Create a new function of int => string
// int => double into double => string
var someMathsFormatted = 
    value => formatResults(someMaths(value));

This is actually really helpful, simple and also very very obvious. Unfortunately as we all normally write functions with multiple arguments this is impossible to use and so we forget it.
In Functional programming this is so common the syntax is simple. Each language is slightly different but here is the F# version:

// someMathsFormatted is a function of int => string
let someMathsFormatted = someMaths >> formatResults

The bad news is that C# does not have the >> operator. No worries, we can write a simple function that does the same although you will see the C# type system imposes a lot of generics based noise.

There is some good news though, the C# compiler type inference has got far better over the years so usage is far cleaner so you only need the generics when you define the function in 99% of cases.

Func<T1, T3> Into<T1, T2, T3>(
    this Func<T1, T2> inner, Func<T2, T3> outer)
{
    return value => outer(inner(value));
}

var someMathsFormatted2 = someMaths.Into(formatResults);

The "this" before the first argument to Into() is C# syntax sugar that allows you to call the function using x.Into(y) syntax instead of Into(x, y). This is called an Extension Method and can make code read far nicer.

It should be noted that the C# way using "this" and Extension methods results in different types of compositions compared to a true functional language and in many cases the parameter order is reversed. The reason behind this is C# requires work for curried functions, Extension Methods are used instead to form pipelines of composition:

// You should have no problems with this
var result = value.Add(5).Divide(2);

It achieves the same goal in a more OO style of writing code.

Time for a couple of quick observation to get you thinking.

  • The function Into() has no idea if it is composing simple functions or functions that are compositions of functions. They are the same thing, true Lego building blocks.
  • Everything so far is still immutable.

Right, Time to ramp up one more notch...

Functions as arguments

Passing functions into other functions is normal in Functional Programming. So common there are a set of functions you will find in most Functional Programming languages. They can be thought of as patterns, if you like to think that way.

They have many names but the most common are below:

  • map mapFunction collection
  • flatmap mapFunction collection
  • filter filterPredicate collection

This is not the full set but gets the ball rolling. I will work through these in turn as there is a lot in these simple functions.

Everyone needs a map

Map sounds simple enough on the surface. It takes a function from listElementType to someOtherType and applies it to each element in the collection producing a new collection containing someOtherType

in C# this is part of the language via LINQ and is called Select():

var intArray = new[] {1, 2, 3};

// The first two samples are the same code in C#. 
// The Extension methods read cleaner sometimes 
// and query syntax others.

// Extension method version
var stringArray1 = intArray.Select(value => value.ToString());

// Query syntax method, almost SQL like
var stringArray2 = from value in intArray select value.ToString;

// We can chain them together. Here we go int => string => int
var intToStringToInt = intArray
    .Select(value => value.ToString())
    .Select(int.Parse);

Ok, that is very abstract so lets assume you have a list of your Person business objects and you want create some xml or other transport objects to send to another system.

xmlPerson CreateXmlPerson(Person person)
{
    return 
        new xmlPerson
        {
            FirstName = person.FirstName,
            LastName = person.LastName,
            ...
        };
} 

// This is nice and simple :)
var xmlPeople = people.Select(CreateXmlPerson);

Now a look at the same code using the Imperative way of thinking, where you say what to do instead of what you want done. I must apologise at this point as I am about to introduce some mutation of variables, the sanity of immutability will resume shortly ;)

// NOT Functional Programming! ;)
List<xmlPerson> CreateXmlPeople(List<Person> people)
{
    var results = new List<xmlPerson>();
    // Mutation of person each pass of the loop
    for (var person in people)
    {
        var xmlPerson = CreateXmlPerson(person);
        // Mutation of the collection
        results.Add(xmlPerson);
    }
    return result;
}
// Sanity resumes

Which do you prefer? I know which I prefer to read.

Why work with all that pointless detail you do not really care about. It is far easier to screw up something with the Imperative style vs the functional style.

It is time to step back and look at something that should be obvious and when explained will be. The signature for map can be expressed in the following way:

map = (T1 => T2) => Collection<T1> => Collection<T2>

This means it takes a function that maps T1 to T2 and creates a function that takes a Collection of T1 and returns a Collection of T2. To look at it another way we have taken a function of T1 => T2 and transformed it into a new function of List of T1 to List of T2.

Cool, you should also spot we are now back to a space where we have a function that takes one argument and returns one result... Yay, we can compose it! :)

As mentioned earlier, The C# signature for Select() has the parameters reversed, it breaks from true functional thinking but we are using the extension method pipeline to compose instead. The principles remain the same though, only the syntax has been changed to protect the innocent.

Flatmap, now we are in a bind :)

From the basic signature flatmap looks exactly the same as map, so time to add some types:

flatmap = (T1 => Collection<T2>) => Collection<T1> => Collection<T2>

OK, this will not have helped in the slightest unless you have encountered flatmap already so time to work through this. First we need some data structures:

class Person
{
    ...
    public List<string> EmailAddresses;
    ...
}

List<Person> people = // populate with people here

Simple enough so far, we have a list of people and each person can have many email addresses. Say we want to get a list of all email addresses so we can do a mail shot, flatmap is the answer.

var emailAddresses = flatmap(person => person.EmailAddresses, people);

So like map, if we just call with the lambda only we are once again back in a space with a function that takes one argument and returns one result. The world of composition is open to us again.

It should be pointed out that the C# LINQ version of flatmap is called SelectMany() and uses the function chaining style of composition:

var allEmailAddresses = people
    .SelectMany(person => person.EmailAddresses);

You can see that the lambda passed into SelectMany returns all the addresses for a person and flatmap just concatenates them together into a single list. Job done!

flatmap is not just to flattern nested data structures, it has many uses but you need to think outside the box. Lets say with have a geographical map with standards coordinates:

  • 1, 2, 3, ... on the X axis
  • A, B, C, ... on the Y axis

This allows us to specify the location 2A and someone will be able to locate that on the map. Say we want a list of all the coordinates:

var xCoordinates = new[] {1, 2, 3};
var yCoordinates = new[] {"A", "B", "C"};

var matrixMultResult =
    xCoordinates.SelectMany(x =>
        yCoordinates.SelectMany(y =>
           new {x, y}));

Yes, this is very ugly and difficult to read unless you are used to it. It is a very common style in functional programming that uses closures to scope the variables. Don't panic, this is where C# query syntax comes into play to save the day. This should make it far more obvious what is going on:

var matrixResults =
    from x in xCoordinates
    from y in yCoordinates
    select new {x, y};

Hang on, isn't this is just nested for loops? Yes it is, but with one subtle change. Ready for the punchline?

We can compose them together, the following gives a collection of all the coordinates as strings :)

var coordinatesAsStrings = matrixResults
    .Select(coordinate => $"{coordinate.x},{coordinate.y}");

But I don't want some of those values

Filter is not a complex function, it takes a function that determines if we want a given value or not, often called a predicate, and a collection. It produces a new collection that only contains the values we want. As with the previous functions, C# has a different name of "Where".

Both these are the same, they produce a new collection only containing people with the required first name:

// Standard shape
var peopleCalledTom = filter(person => person.Name == "Tom", people);

// C# Shape
var peopleCalledBob = people.Where(person => person.Name == "Bob");

I doubt I need to point this out by now, all the standard composition rules already highlighted apply here also.

This is a lot to take in so time for another break. Just a few quick observations first:

  • You should be starting to see how the limitation we imposed on functions have given us a neat way of chaining them into a pipeline of discrete operations.
  • It is easy to see what is going on in the code without diving into the detail, each function does its job and is obvious in what it does.
  • At this point in the series you actually have almost all you need to understand the scary world of Monads, you just do not realize it yet :)

As always, feedback is welcome. Am I moving too fast, not fast enough etc?

Woz

Part 4 is now available.

Sort:  

As soon as I see int.Parse, I recognize c#! Way to show off the language.

Thanks. C# started as a piss poor Java clone but has grown into a great expressive language if you are willing to push the syntax :)

Wish it had higher kinded types but then things get real complex

Coin Marketplace

STEEM 0.20
TRX 0.14
JST 0.030
BTC 66205.77
ETH 3304.04
USDT 1.00
SBD 2.72