Trustbit

View Original

Function Composition in F# with Unfriendly Functions

Introduction

This is a short post looking at how to solve the problem of using F# unfriendly libraries in an F# function composition pipeline. One of my colleagues asked a question about this and I thought it might be interesting to look at some of the options available to us to solve it.

Setting Up

You can use any IDE but I will be using VSCode plus the wonderful ionide plugin.

Open VSCode in a new folder.

Open a new VSCode Terminal and create a new console app using:

See this content in the original post

In the VSCode Explorer mode, add a new folder called resources. Add a new file to the resources folder called employees.json.

Copy the following into the new file:

See this content in the original post

Replace the code in program.fs with the following:

See this content in the original post

Run the code by typing the following into the terminal and pressing Enter:

See this content in the original post

You should see some json in the terminal window.

Introducing the Problem

Whilst this works, what happens if I want to add some JsonSerializerOptions like this?

See this content in the original post

You will need to download a nuget package:

See this content in the original post

The Deserialize method has a version that takes a tuple of string and JsonSerializerOptions.

See this content in the original post

This doesn't even compile. Even if the parameters were swapped, it still wouldn't work because we are dealing with a tuple rather than curried arguments. Luckily, this isn't a difficult thing to solve but there are a few ways that we could resolve it. We are going to look at three viable solutions.

Version 1 - Custom Wrapper Function

The general approach to solving these types of problems is a layer of indirection; In this case a wrapper function.

See this content in the original post

We have wrapped our unfriendly method in a usable curried wrapper. Let's modify the getEmployees function to use this new function:

See this content in the original post

If you run it, you will see that it works as before.

We can take this even further by adding a partially applied version of the new wrapper function with the options already set, so that we only need to apply the last argument to make it run.

See this content in the original post

Again, we update the getEmployees function to use the new partially applied wrapper function:

See this content in the original post

If we run this, we will still see the expected results.

Version 2 - Generic Higher Order Function

Another option is to create a generic, in both senses of the word, function that can handle all situations that require this specific functionality:

See this content in the original post

This is the ultimate goal of pure functional programming: to find generic solutions to problems.

Now we can fit our new function into the pipeline where it will be ready to accept the last partially applied parameter through the pipeline:

See this content in the original post

Having said that this is a good thing to do, it is nowhere nearly as readable at a glance as the previous solution. Purity does not always imply superiority.

Version 3 - Inline Function

If this is a one-off requirement, rather than creating additional partially applied functions, we could use an inline anonymous function instead:

See this content in the original post

This is a really simple and elegent solution and would probably be the first approach we should try. If we needed to use the same logic elsewhere, we would start to consider using a custom wrapper function instead.

Summary

In this post we have had a look at ways to solve the problem of fitting unfriendly functions into an F# composition pipeline. We haven't used anything that wasn't covered in my 12 part Introduction to Functional Programming in F# series.

If you have any comments on this post or suggestions for new ones, send me a tweet (@ijrussell) and let me know.