Functional programming for the OO developer (Part 2)
Now the basic groundwork has been done in Part 1 we can move on. This will start to ramp up a little!
How to make functions far less useful
First we need to change/break your concept on what a function really is. This will feel counter intuitive at first, as though though arbitrary limits are being imposed without any gain. Stick with it, the payoff is worth it.
We are all used to functions, we have been writing them forever. We have all seen this sort of stuff all over the place?
public string DoSomeStuff(int a, int b)
{
Console.WriteLine("Some logging?");
this.DoSomeStuffCount++;
var r = a + b + new Random.Next();
if (r = 0)
{
throw new Exception("Not allowed");
}
return r;
}
The trouble is this is not a function in the mathematical sense. I takes values and returns a result but that is where it end. It has many problems:
- The signature is a lie. It claims to be (int, int) => string but it can throw an exception.
- It does IO, another possible set of exceptions or different effect each time.
- It could be called with the same arguments many times and give a different result each time.
- It updates global state, the class instance field.
- It does not compose. You can't plug the results of a function into the two arguments in this function.
- You have to read its code to understand it because of the side effects
These problems are easy to solve but you will not like it, I am going to break what you can do with a function to make them more flexible, easier to reason about, easier to refactor and simple to compose. From now on functions:
- Are only static methods
- Have one argument and return one result.
- A given input will ALWAYS return the same result.
- You can't throw exceptions.
- No IO.
- No global state.
What have you done to my functions, aaaaarrrrgh
I agree, on the surface this sounds horrible! So lets knock off these points one by one.
The first point is easy enough to deal with, because the same is actually true in most OO languages. Instance methods are simply static methods with an invisible first argument, this, which the compiler wires up. There might be some vtable magic with virtual routing but this is all still just static functions. In Functional Programming you are responsible for passing in all the arguments you need instead of the compiler.
The second point sounds impossible to live with, given I have just told you you only have one argument you can pass in, not so.
The slight of hand used to allow multiple arguments comes from Lambda and the closures they provide (1). This is shown below in C#:
// Func with two arguments that returns the sum
int Add(int a, int b)
{
return a + b;
}
var result1 = Add(1, 2); // Equals 3
// And now as a lambda, still (int, int) => int
Func<int, int, int> addFunc = (a, b) => a + b;
var result2 = addFunc(a, b); // Equals 3
// And now as a one argument function. An int => (int => int)
Func<int, Func<int, int>> curriedAddFunc = a => b => a + b;
var result = curriedAddFunc(1)(2); // Equals 3, two function calls
// Wait, wtf does this mean?
var partial = curriedAddFunc(1);
You are probably fine until we get to the curried function so lets take a deeper look. The curried function is a function that takes an int value, stores it in a closure, then returns a function that takes the second int. When supplied the second argument the sum is calculated.
Closures can take a while to really understand if you read more technical posts but they are very simple. Take the following:
int GetAge(IEnumerable<Person> people, string name)
{
return people
.Where(person => person.Name == name)
.Select(person => person.Age);
}
The lambda passed to Where() requires a closure, in an OO sense Where() is passed the following:
class WhereClosure
{
private string name;
public WhereCloseure(string name)
{
this.name = name;
}
public int Filter(Person person)
{
return person.Name = this.name;
}
}
With this knowledge you should now be able to see that addFunc and curriedAddFunction are the same, the only thing different being how they are invoked. The curried version allows you to supply one argument at a time.
On the surface this looks like semantic BS for the sake of it, for Languages like F# and Haskell this is built in. Here is the F# syntax, all functions are curried:
let add a b = a + b
let add1 = add 1
let result = add1 2
Lets wrap up this point with a concrete example of real world use, we will revisit it in more detail later. The following example takes a person lookup function, think dependency injection of the getPerson interface.
Report BuildReport(Func<int, Person> getPerson)
{
// Build my super special person report
....
var person = getPerson(personId);
....
return report;
}
Unfortunately our get person function looks like this:
Func<string, Func<int, Person>> getDbPerson =
connectionString => id =>
{
// Get the person from the connection
return person;
}
No worries, if we call it with just the connection string we get an int => Person, the connection is baked into the function. Now we can simply call our report like this:
// Direct pass
var report1 = BuildReport(getDbPerson(connectionString));
// Long hand
var getPerson = getDbPerson(connectionString);
var report2 = BuildReport(getPerson);
You should be able to see how simple it is to replace the database access with in memory lookup using the same technique. Now think through the ramifications that has for unit testing, no need for mocking frameworks at all! No boiler plate interfaces, just give me a function from int to Person and I will do my thing.
This Partial Application of functions has many uses. Say you have a 3D vector library but your next project is 2D. If your functions were curried and Z was the first parameter you simply call all the functions with one parameter 0 and now you have a set of functions that work in 2D. Let that sink in for a while, with the 3D to 2D example we have reshaped an API through partial application, You should also be able to see the order of parameters really matters.
A valid question at this point might be. With a complex program many closures can be in play at the same time, isn't this a complex bug waiting to happen?
In an OO world, yes. Something could change a property on you and then the program does something stupid that you did not expect. Remember, we are in immutable world. Nothing can change what is under your feet.
We will deal with the last three points at a basic level here, those being: Exceptions, IO and State as they are related.
Even with a pure language like Haskell (2) which enforces these rules there are cheats to work around them, without IO programs are pointless. Haskell uses the scary world of Monads (3) to solve this problem. Programs are structured such that you push the IO out of your core code to the fringe and wrap it in abstractions that handle the issues for you. The bulk of your code should be simple logic, transformation and computation with pure functions that are simple to reason about. Take in an input and also give the same output for that input.
In the next part we will explore why we treat functions this way, the world of composition.
Woz
Part 3 is now available.
(1) Do I need to dig into closures deeper?
(2) You should look at Haskell, very interesting language. Pure as the driven snow :)
(3) Not scary at all, very simple but people make them hard when they explain them.