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:

// int * int -> int
let add (a:int, b:int) : int = a + b

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:

// int * int -> int
let add (a, b) = a + b

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

// int
let result = add (2, 3) // returns 5

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

// int
let result = add(2, 3) // returns 5

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:

// int -> int -> int
let add a b = a + b

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:

// int
let result = add 2 3 // returns 5

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

// int -> int
let result = add 2

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.

// int
let result2 = result 3 // returns 5

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:

// string -> int -> string
let example (a:string) (b:int) =
    String.replicate b a 

// string
let runExample = example "Hello" 2 // returns "HelloHello"

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?

let runExample = example 2 // Error - you can't do this

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:

// int -> string
let runExampleHello = example "Hello"

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:

// string
let result = runExampleHello 5 // returns "HelloHelloHelloHelloHello"

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

// int -> string
let runExample i = example "Hello" i

let result = runExample 2 // returns "HelloHello"

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

// int -> string
let runExample i = i |> example "Hello"

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:

// (int -> int -> int) -> int -> int -> int
let calculate (f:int -> int -> int) a b = f a b

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:

// int
let result = calculate add 2 3 // returns 5

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

// int -> int -> int
let multiply a b = a * b

We use in the same way:

// int
let result = calculate multiply 2 3 // returns 6

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

// int -> int
let result = calculate multiply 2

Adding the required last argument give us:

// int
let result2 = result 3 // returns 6

I can pass in an anonymous function if I like:

// int
let result = calculate (fun a b -> a + b) 2 3

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

// ?
let multiply = calculate (fun a b -> a * b)

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:

type Customer = {
    Id : int
    Name: string
}

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

// string -> int -> Customer
let getCustomerFromDb (connString:string) (id:int) =
    // Imagine we are getting data from a Db. 
    // We are not handling missing data [Option or Result]
    { Id = 1; Name = "Real Customer" }

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

// string -> int -> Customer
let doStuff (connString:string) (id:int) = 
    let customer = getCustomerFromDb "my_conn_string" id
    customer

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:

// (int -> Customer) -> int -> Customer
let getCustomer (getCustomerService:int -> Customer) (id:int) =
    getCustomerService id

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:

// string -> int -> Customer
let doStuff (connString:string) (id:int) = 
    // int -> Customer
    let customerService = getCustomerFromDb "my_conn_string"
    let customer = getCustomer customerService id
    customer

We could rewrite it to look like this:

// string -> int -> Customer
let doStuff (connString:string) (id:int) = 
    let customer = getCustomer (getCustomerFromDb "my_conn_string") id
    customer

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

// int -> Customer
let getCustomerStub (id:int) =
    { Id = 1; Name = "Test Customer" }

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

// unit -> bool
let ``should return a valid customer`` () =
    let expected = { Id = 1; Name = "Test Customer" }
    let customer = getCustomer getCustomerStub expected.Id
    customer = expected

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:

// int -> int -> int
let add a b = a + b

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:

// int -> int -> int
let add a = fun b -> a + b

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:

// int -> (int -> (int -> int))
let add a =
    fun b ->
        fun c -> a + b + c

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

Blog 5/18/22

Introduction to Functional Programming in F#

Dive into functional programming with F# in our introductory series. Learn how to solve real business problems using F#'s functional programming features. This first part covers setting up your environment, basic F# syntax, and implementing a simple use case. Perfect for developers looking to enhance their skills in functional programming.

Blog 3/11/21

Introduction to Web Programming in F# with Giraffe – Part 2

In this series we are investigating web programming with Giraffe and the Giraffe View Engine plus a few other useful F# libraries.

Blog 5/17/23

Introduction to Functional Programming in F# – Part 10

Discover Agents and Mailboxes in F#. Build responsive applications using these powerful concurrency tools in functional programming.

Blog 3/10/21

Introduction to Web Programming in F# with Giraffe – Part 1

In this series we are investigating web programming with Giraffe and the Giraffe View Engine plus a few other useful F# libraries.

Blog 7/21/20

Understanding F# applicatives and custom operators

In this post, Jonathan Channon, a newcomer to F#, discusses how he learnt about a slightly more advanced functional concept — Applicatives.

Blog 8/8/23

Introduction to Functional Programming in F# – Part 12

Explore reflection and meta-programming in F#. Learn how to dynamically manipulate code and enhance flexibility with advanced techniques.

Blog 8/7/20

Understanding F# Type Aliases

In this post, we discuss the difference between F# types and aliases that from a glance may appear to be the same thing.

Blog 3/12/21

Introduction to Web Programming in F# with Giraffe – Part 3

In this series we are investigating web programming with Giraffe and the Giraffe View Engine plus a few other useful F# libraries.

Blog 9/13/22

Introduction to Functional Programming in F# – Part 2

Explore functions, types, and modules in F#. Enhance your skills with practical examples and insights in this detailed guide.

Blog 12/22/22

Introduction to Functional Programming in F# – Part 6

Learn error handling in F# with option types. Improve code reliability using F#'s powerful error-handling techniques.

Blog 12/22/22

Introduction to Functional Programming in F# – Part 7

Explore LINQ and query expressions in F#. Simplify data manipulation and enhance your functional programming skills with this guide.

Blog 10/1/22

Introduction to Functional Programming in F# – Part 4

Unlock F# collections and pipelines. Manage data efficiently and streamline your functional programming workflow with these powerful tools.

Blog 3/22/23

Introduction to Functional Programming in F# – Part 8

Discover Units of Measure and Type Providers in F#. Enhance data management and type safety in your applications with these powerful tools.

Blog 7/12/23

Introduction to Functional Programming in F# – Part 11

Learn type inference and generic functions in F#. Boost efficiency and flexibility in your code with these essential programming concepts.

Blog 3/22/23

Introduction to Functional Programming in F# – Part 9

Explore Active Patterns and Computation Expressions in F#. Enhance code clarity and functionality with these advanced techniques.

Blog 10/11/22

Introduction to Functional Programming in F# – Part 5

Master F# asynchronous workflows and parallelism. Enhance application performance with advanced functional programming techniques.

Blog 9/15/22

Introduction to Functional Programming in F# – Part 3

Dive into F# data structures and pattern matching. Simplify code and enhance functionality with these powerful features.

Wissen 4/14/23

Exceptionally user-friendly thanks to SAP Fiori

The user-friendliness of an operational application determines to a large extent whether the process associated with the application is successfully used and implemented in the company. This applies in particular to idea and innovation management programs that rely on the motivation and participation of employees.

Referenz

Introduction of Jira to Hamburger Hochbahn

The Hamburger Hochbahn AG controls the development of its new mobility platform "Switchh" via the Atlassian project management tool Jira – introduced, administered and hosted by the TIMETOACT GROUP.

Blog 12/3/21

Using Discriminated Union Labelled Fields

A few weeks ago, I re-discovered labelled fields in discriminated unions. Despite the fact that they look like tuples, they are not.

Bleiben Sie mit dem TIMETOACT GROUP Newsletter auf dem Laufenden!