Trustbit

View Original

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:

See this content in the original post

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:

See this content in the original post

To use the add function, we can do the following:

See this content in the original post

You can also lose the space after the binding name if you wish:

See this content in the original post

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:

See this content in the original post

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:

See this content in the original post

No real difference is there? The fun happens when we ask 'What happens if I only supply the first argument?':

See this content in the original post

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.

See this content in the original post

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:

See this content in the original post

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?

See this content in the original post

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:

See this content in the original post

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:

See this content in the original post

If you want to, you can make the input parameter explicit so that you can change it easily:

See this content in the original post

I could also rewrite the function to use the forward pipe operator:

See this content in the original post

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:

See this content in the original post

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:

See this content in the original post

Let's create a new function that matches the signature that multiplies instead:

See this content in the original post

We use in the same way:

See this content in the original post

If we decide not to pass in the last argument, we get:

See this content in the original post

Adding the required last argument give us:

See this content in the original post

I can pass in an anonymous function if I like:

See this content in the original post

What do you think the function signature of the following multiply function is?

See this content in the original post

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:

See this content in the original post

Then create a function that simulates a database call to get a Customer:

See this content in the original post

Finally, we create a function that uses the db function:

See this content in the original post

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:

See this content in the original post

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:

See this content in the original post

We could rewrite it to look like this:

See this content in the original post

We would then add a test module and include the following helper function that we will use instead of the database calling function:

See this content in the original post

Finally, we write a test that calls the main doStuff function:

See this content in the original post

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:

See this content in the original post

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:

See this content in the original post

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:

See this content in the original post

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. :)

https://twitter.com/ijrussell