Function Composition in F# with Unfriendly Functions

by Ian Russel | Data Analyst/Architect/Developer

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:

dotnet new console -lang F#

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:

{
    "Employees": [
        {
            "Name": "Ted",
            "Email": "ted@nomail.com",
            "Age": 24
        },
        {
            "Name": "Doris",
            "Email": "doris@nomail.com",
            "Age": 31
        },
        {
            "Name": "John",
            "Email": "john@nomail.com",
            "Age": 48
        },
        {
            "Name": "Clarice",
            "Email": "clarice@nomail.com",
            "Age": 39
        }
    ]
}

Replace the code in program.fs with the following:

open System.IO
open System.Text.Json
open System.Text.Json.Serialization

type Employee = {
    Name : string
    Email : string
    Age : int
}

type EmployeeList = {
    Employees: Employee array
}

// string -> EmployeeList
let getEmployees path = 
    path
    |> File.ReadAllText
    |> JsonSerializer.Deserialize<EmployeeList>

[<EntryPoint>]
let main argv =
    let message = getEmployees "resources/employees.json"
    printfn "%A" message
    0 // return an integer exit code

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

dotnet run

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?

let defaultOptions =
    let options = JsonSerializerOptions()
    options.Converters.Add(JsonFSharpConverter(JsonUnionEncoding.NewtonsoftLike, allowNullFields = true))
    options

You will need to download a nuget package:

dotnet add package FSharp.SystemTextJson

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

let getEmployees path = 
    path
    |> File.ReadAllText
    |> JsonSerializer.Deserialize<EmployeeList>(?, defaultOptions)

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.

// JsonSerializerOptions -> string -> EmployeeList
let deserialize<'T> options (data:string) = 
    JsonSerializer.Deserialize<'T>(data, options)

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

let getEmployees path = 
    path
    |> File.ReadAllText
    |> deserialize<EmployeeList> defaultOptions

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.

let deserializeWithDefaultOptions<'T> = deserialize<'T> defaultOptions

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

let getEmployees path = 
    path
    |> File.ReadAllText
    |> deserializeWithDefaultOptions<EmployeeList>

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:

// ('a * 'b -> 'c) -> 'b -> 'a -> 'c
let reverseTuple f x y = 
    f(y, x)

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:

let getEmployees path = 
    path
    |> File.ReadAllText
    |> reverseTuple JsonSerializer.Deserialize<EmployeeList> defaultOptions

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:

let getEmployees path = 
    path
    |> File.ReadAllText
    |> fun data -> (data, defaultOptions)
    |> JsonSerializer.Deserialize<EmployeeList>

 

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.

2/19/21
Blog 11/30/22

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.

Blog

Solving Transport Tycoon 2 Episode 2.1 With F#

Solve Transport Tycoon 2.1 in F#: explore recursion, rose trees, and functional techniques to compute the shortest railway routes between stations.

Blog

Functional Validation in F# Using Applicatives

Learn how to implement functional validation in F# using applicatives. Handle multiple errors elegantly with Railway-Oriented Programming and concise functional patterns.

Blog

Using GCP Cloud Functions with F#

Learn how to build and test Google Cloud Functions in F#, using dependency injection, configuration, and pub/sub messaging for real-world cloud apps.

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

Leistung 9/22/20

Working with CLOUDPILOTS

With the perfect partner at your side, many things are easier and more efficient, although a direct relationship with the manufacturer is appealing at first glance. But only at first glance!

Blog

Digitization with security

Conditions of the German economy for the cloud: what companies in Germany expect from secure and sovereign cloud solutions

novaCapta: Ihr Partner für die digitale Transformation mit Microsoft Technologien
Service

Intranet with SharePoint

For many employees, the intranet is the door to collaboration and networking. Use the potential of Office 365 to take full advantage of the opportunities.

young business woman smiles it jobs timetoact group
Jobs

(Senior) Consultant SAP FI (f/m/d)

Walldorf Consulting | Walldorf | Full-time & Permanent employment | Available now

two colleagues working together it jobs timetoact group
Jobs

(Senior) Consultant SAP MM (f/m/d)

Walldorf Consulting | Walldorf | Full-time & Permanent employment | Available now

team of young colleagues it jobs timetoact group
Jobs

(Senior) Consultant SAP CO (f/m/d)

Walldorf Consulting | Walldorf | Full-time & Permanent employment | Available now

young business man smiles it jobs timetoact group
Jobs

(Senior) Consultant SAP SD (f/m/d)

Walldorf Consulting | Walldorf | Full-time & Permanent employment | Available now

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.

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

Bleiben Sie mit dem TIMETOACT GROUP Newsletter auf dem Laufenden!