Functional programming for the OO developer (Part 4)

in #programming7 years ago (edited)

Last time in Part 3 we learnt that functions as arguments is easy enough and really helpful. Time to turn the volume up to 11 now!

Functions are data

So far you should be happy that functions can create functions and that any curried function is actually a function factory. We can also pass these generated functions as arguments to other functions. I am now going to show another way to use these to build more complex operations.

Lets start with the following code:

var bobs = people.Where(person => person.Name == "Bob");

We are going to extract the predicate and make a function factory from it.

Func<Person, bool> HasNameOf(string name)
{
    return person => person.Name == name;
}

So far this feels like a backwards step, we still have the same predicate code but now we have wrapped it in a function. The first thing this gets us is readability of code as show here:

var bobs = people.Where(HasNameOf("Bob"));

Even someone without much coding experience can see what is going here but is it really better?

There are a few points of interest:

  • It does generate Person predicates that we can use elsewhere without duplicate code.
  • It reads well, Almost English.
  • Predicates are helpful in other places apart from Where()
  • Only one place to bug fix.

So some small benefits and code reuse. There has to be something else it buys us before we start writing all predicates this way. What if we want to apply a number of predicates like this:

// These are both the same in LINQ (1)

var bobs = people.Where(person => 
    person.Name == "Bob" && 
    person.Age == 30 &&
    person.Town == "Christchurch");

var bobs = people
    .Where(person => person.Name == "Bob")
    .Where(person => person.Age == 30)
    .Where(person => person.Town == "Christchurch");

Both are easy enough to understand, we will only select a given Bob if he is also 30 years old and lives in "Christchurch".

Hang on, what if we could compose them together? Wait forget that they do not fit together, bool does not plug into Person :(

Actually they will compose, we just need to create a way to compose them. Lets start by writing the last of the factories we need for the predicates:

Func<Person, bool> HasNameOf(string name)
{
    return person => person.Name == name;
}

Func<Person, bool> HasAgeOf(int age)
{
    return person => person.Age == age;
}

Func<Person, bool> ThatLivesIn(string town)
{
    return person => person.Town == town;
}

So far so good. Nice simple functions that do one thing well and hard to screw up, unless it is a copy paste error :)

You can start to see a pattern now, all return functions of Person to bool. We just need a way to glue them together. In this case we want to "and" them together, so how about this:

Func<Person, bool> AllOf(
    params Func<Person, bool>[] predicates)
{
    return person => 
        perdicates.All(predicate => predicate(person));
}

var calledBobAged50InChristchurch = AllOf(
    HasNameOf("Bob"), HasAgeOf(50), ThatLivesIn("Christchurch"));

var bobs = people.Where(calledBobAged50InChristchurch);

There are a couple of pieces of syntax I should explain here. I have prefixed the argument with params, in C# this means you can call the function as shown above with an arbitrary number of People predicates or pass it an array of predicates. Great bit of syntax sugar.

All() is a LINQ command, it applies a predicate to all items in a collection and returns true if the predicate was true for all items. The way we are using it we get given a person, we apply each of the predicates to the person and all have to pass to return true.

The part you need to wrap your head around is that the collection is a collection of functions, the functions are the data we are using the LINQ query on.

Functions are data and data can be functions. They are one and the same thing to a Functional Programmer. This was probably the hardest concept for me to get comfortable with on my journey down the rabbit hole. (2)

So now all we need is an "or" like operation and we are good to go:

Func<Person, bool> OneOf(
    params Func<Person, bool>[] predicates)
{
    return person => 
        perdicates.Any(predicate => predicate(person));
}

// Fun time
var matches = AllOf(
    HasAgeOf(30), OneOf(HasNameOf("Bob"), HasNameOf("Tom")));

All I should need to describe here is Any(). This is like All() but returns true if any of the items in the collection match the predicate. So the following are the same. In the first case we do not need the Func type as the compiler can infer the type:

var matches = AllOf(
    HasAgeOf(30), OneOf(HasNameOf("Bob"), HasNameOf("Tom")));

Func<Person, bool> matches = person => 
    person.Age == 30 &&
    (person.Name == "Bob" || person.Name == "Tom"); 

This might all sound very arbitrary and abstract but if you have managed to follow this part and understand it you have learnt some very powerful and important concepts:

  • You can create business rules engines from small composable blocks of lego.
  • It is very hard to mess up any of the functions, they are that simple.
  • They are so easy to plug together, less chances for bugs.
  • Each function can be unit tested in a couple of lines of code.
  • If you go back and look carefully you will realise these functions are constructed AT RUN TIME and capturing data via closures. Much like when you "new" an class in OO.
  • You can dispose of these functions in the same way you would a class.
  • Functions really are data and data can be functions.
  • This is actually very similar to how a compiler parses source code.

I think it is now time for a pause for people to catch up. Post questions and feedback.

(1) I will dig into how LINQ works and the pitfalls for new players in another series.
(2) Actually the state Monad was the worst, but that is a much later story.

Sort:  

GREAT POST :D :D :D

Follow and vote @cryptonnn .. I will follow you back , and vote :)

Nice series, but a lot to read. :) I just recently learned functional programming in java. This new feature is very cool and as a guy that has never gone into functional programming is this a very interesting way to solve problems.

Thanks, it was a lot of information to get down but I tried to keep it to the minimum for what I had to get across. Did it work?

Are you on Java 8? They added a lot of goodness and much of what I have said will apply to Streams, that appears to be Java 8 LINQ :)

I still have a lot to get across, but will pause for a week or so and let people catch up :)

As a side note, The Java 8 streams appear to support other monads apart from list. Think it comes with optional :) Never have to face null again.

Coin Marketplace

STEEM 0.20
TRX 0.14
JST 0.030
BTC 66683.89
ETH 3311.03
USDT 1.00
SBD 2.70