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

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

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

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

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

Bleiben Sie mit dem TIMETOACT GROUP Newsletter auf dem Laufenden!