Introduction to Partial Function Application in F#
Partial Function Application is one of the core functional programming concepts that everyone should understand as it is widely used in most F# codebases.
In this post I will introduce you to the grace and power of partial application. We will start with tupled arguments that most devs will recognise and then move onto curried arguments that allow us to use partial application.
Tupled Arguments
Let's start with a simple function called add that takes a tuple of ints and returns an int as the result:
Throughout this post, we will be concentrating on the function type signatures, in this case int * int -> int
. Whenever you see an arrow ->
in a type signature, that implies a function. The name (binding) add is a function that takes a tuple (defined by the *
) of int and int as input, and returns an int
as the result.
The F# compiler is able to infer most types through usage, so we can rewrite our function without the types if we wish:
To use the add function, we can do the following:
You can also lose the space after the binding name if you wish:
Tupled arguments are a useful tool but they tend to be used less than the other form of function arguments, curried, because they don't support partial application.
Curried Arguments
This is the same function written using curried arguments:
No commas or brackets but more importantly, a change in the function signature to int -> int -> int
. I will cover the meaning of the multiple arrows later in the post but for now, let's continue with how to use this function:
No real difference is there? The fun happens when we ask 'What happens if I only supply the first argument?':
It doesn't error! Instead, if we look at the signature, result is a function that takes an int and returns an int! If I then supply the second argument, I will get the actual value from the function.
This is Partial Function Application. The ability to use a subset of the arguments and to get a result only when all of the other required arguments are supplied. Let's have a look at another example:
We have a function that takes a string and an int and returns a string.
What happens if I supply a subset of the arguments out of order?
It doesn't work. You must supply the argumants in order. Ordering of arguments, particularly the last one, but also as we will see later, the first one(s) matters.
Let's try partially applying the arguments and we should get a function that takes an int and returns a string:
I've gone from string -> int -> string
to int -> string
after supplying the first string argument. If I now supply the number of repetitions argument, it will return a string result because I have supplied all of the required arguments:
If you want to, you can make the input parameter explicit so that you can change it easily:
I could also rewrite the function to use the forward pipe operator:
The implication of this is that forward piping uses partial application.
Passing Functions as Parameters
Functions are first class citizens in F#. This means that we can do interesting things with them like pass them as arguments into other functions:
Our type signature shows that we pass in a function that takes two ints and returns an int plus two other ints and returns an int.
Let's call the calculate function with the add function we created earlier as it has a type signature that matches f:int -> int -> int:
Let's create a new function that matches the signature that multiplies instead:
We use in the same way:
If we decide not to pass in the last argument, we get:
Adding the required last argument give us:
I can pass in an anonymous function if I like:
What do you think the function signature of the following multiply function is?
Hopefully, you will agree that it is int -> int -> int
.
Realish example
This example will use the function injection ideas to allow us to test some code that has side effects.
We'll create a record type of Customer:
Then create a function that simulates a database call to get a Customer:
Finally, we create a function that uses the db function:
How do we test this without using a database? There are a few options but we are going to use partial application. The first thing we will do is create a helper function that sits between the previous two functions that we will inject a function into:
We then modify the main caller function so that we can pass in a partially applied database function by only providing the first string argument:
We could rewrite it to look like this:
We would then add a test module and include the following helper function that we will use instead of the database calling function:
Finally, we write a test that calls the main doStuff function:
Partial application is a nice way to help keep your functions with side effects away from your core business logic functions.
Finally, why do we have multiple arrows in the add function?
Under the covers
We have our add function that takes two arguments (int and int) and returns an int:
Actually, that is a lie. Functions in F# take one argument as input and return one item as output. So what's going on? Firstly we will rewrite or function in a slightly different style:
It has the same signature as before but we could read the code as 'the function add takes an int 'a' as input and returns a function that takes int 'b' as input and returns an int result'. It might help to write the signature as int -> (int -> int)
. If we extend it to three inputs, we get:
In this case, add is a function that takes an int a
as input and returns a function that takes an int b
as input and returns a function that takes an int c
as input and returns an int
as output.
In practical terms, knowing what goes on under the covers doesn't actually have much impact as you can treat a function with multiple input arguments as just that but it's important to know that not providing all of the argumants results in a function being returned with the remaining unsatisfied arguments instead of a value.
Further Example
If you look at my Functional Validation in F# Using Applicatives post for last year's calendar, you'll see that that makes use of the techniques that we have used today for the happy path.
Summary
Using curried arguments opens the door to partial function application in F#. It is one of the most powerful and useful techniques we have for functional programming in F#.
Understanding what your function signatures are telling you is important, as is trusting the compiler.
Thanks to Sergey Tihon for F# Weekly and for running the F# Advent Calendar, Scott Wlaschin for writing https://pragprog.com/book/swdddf/domain-modeling-made-functional and making https://fsharpforfunandprofit.com/ such an amazing resource plus special thanks to all of you in the F# community for being so awesome. :)