Function Composition

During a lunchtime conversation with Dustin Cambell, of Did it with .NET fame, the topic of Function Composition came up. I am a huge fan of the pipe-forward operator (|>) but have never found a use for the function composition operator (>>). Dustin pointed me to his latest blog post and after reading it, realized the error of my ways. Function Composition is one of those those foundational skills that you should definitely try to master in F#.

So what is Function Composition? Function Composition is composing new, more powerful functions based on smaller ones. Let’s say your task is to compute the size on disk of all the files under a certain path and you have the following helper routines:

Basic Functions


open System
open System.IO

// Gets all files under a given folder
let rec filesUnderFolder rootFolder = 
    seq {
        for file in Directory.GetFiles(rootFolder) do
            yield file
        for dir in Directory.GetDirectories(rootFolder) do
            yield! filesUnderFolder dir }

// Gets the information about a file
let fileInfo filename = new FileInfo(filename)

// Gets the file size from a FileInfo object
let fileSize (fileinfo : FileInfo) = fileinfo.Length

// Converts a byte count to MB
let bytesToMB (bytes : Int64) = bytes / (1024L * 1024L)

The old-fashioned, imperative programming way of calculating the result would be to call each function in sequence, store the result, and pass it into the next function.

Lame, Imperative Code

// Doing things the lame way
let photosInMB_lame =
    let folder = @"C:\Users\chrsmith\Pictures\"
    let filesInFolder = filesUnderFolder folder
    let fileInfos     = fileInfo filesInFolder
    let fileSizes     = fileSize fileInfos
    let totalSize     = Seq.fold (+) 0L fileSizes
    let fileSizeInMB  = bytesToMB totalSize

We can improve upon this by using the pipe-forward operator (|>) which is defined as:

let inline (|>) x f = f x

The pipe-forward operator allows you to rearrange the ordering of a function so that the argument comes before the function. Using this then, you can ‘pipe’ multiple functions together and avoid the need for temporary variables. So here we take the file path and pipe it to the ‘filesUnderFolder’ function, then pipe the sequence of files to ‘ fileInfo’, then pipe that result to ‘ fileSize’, and so on.

Better, Using Pipe-Forward Operator

// Using the Pipe-Forward operator (|>)
let photosInMB_pipeforward =
    |> filesUnderFolder
    |> fileInfo
    |> fileSize
    |> Seq.fold (+) 0L 
    |> bytesToMB

The solution using the pipe-forward operator is pretty elegant. But it contains a subtle problem in that you need to provide the initial argument to begin the piping process. So in order to reuse this code you need something like:

// A reusable function using Piping
let photosInMB_reusable baseFolder =
    |> filesUnderFolder
    |> fileInfo
    |> fileSize
    |> Seq.fold (+) 0L 
    |> bytesToMB

And to be honest, that’s the way I’ve been writing a lot of code lately. Create a function that takes an argument and then pass that argument into a series of piped-functions back to back. But alas, there is a better way!

Introducing the Function Composition operator (>>):

let inline (>>) f g x = g(f x)

Which reads as: given two functions, f and g, and a value, x, compute the result of f of x and pass that result to g. The interesting thing here is that you can curry the (>>) function and only pass in parameters f and g, the result is a function which takes a single parameter and produces the result g ( f ( x ) ).

Here’s a quick example of composing a function out of smaller ones:

let negate x = x * -1
let square x = x * x
let print  x = printfn "The number is: %d" x

let square_negate_then_print = square >> negate >> print
asserdo square_negate_then_print 2

Which when executed prints ‘-4’. We have successfully joined several functions together, like we did using (|>) but we didn’t need to declare a parameter and pass that to our function chain. Instead we rely on a partial application of (>>). We can now rewrite our original directory crawler using function composition:

// Using the Function-Composition operator (>>)
let getFolderSize = 
    >> fileInfo 
    >> fileSize 
    >> Seq.fold (+) 0L 
    >> bytesToMB
let photosInMB_funccomp = getFolderSize @"C:\Users\chrsmith\Pictures\"

Think of function composition (>>) just like using pipe-forward (|>) except you don’t need to specify the first parameter up front. Using (>>) will allow for slightly more concise code and provide a new way to think about applying your functions.

Comments (2)

  1. I’m writing this from the ‘big room’ in the LA Convention Center.If you don’t know, PDC is a HUGE conference