F#: Composing Functions

Following on from my last post, and in preparation for a series on producing a full WPF application in F#, in this post I discuss the differences between the |> and >> operators and some additional functions I find helpful for composing functions.

The pipeline operator

The pipeline operator, |>, is fairly easy to understand. It allows a program to be rewritten to reduce the number of parentheses. For example, if I have the function:

let multiply x y = x*y

then I can write:

let double x = x |> multiply 2

and that is equivalent to:

let double x = multiply 2 x

In other words, the pipeline operator takes the left operand (in this case x) and applies it to the curried function given as its right operand. Because of precedence and associativity rules,

x |> multiply 2

is usually interpreted as

x |> (multiply 2)

The pipeline operator gets its name (although I do not know the true origins!) because it can be composed into a string of function applications:

x |> f a |> g b c |> h

or more commonly:

```   x
|> f a
|> g b c
|> h```

Both are equivalent to

h (g b c (f a x))

I know which variation I find easier to read.

The double pipeline operator (||>)

Note: apart from the pipeline operator, none of those discussed in this post are described using official, standard names.

Sometimes, it is helpful to have a pair (or another tuple), (‘a*’b), and apply it to a curried function expecting two arguments ‘a->’b->’c. This is precisely what the double pipeline operator does ||>, for example:

(2,3) ||> multiply.

Limitations of the pipeline operator

The pipeline operator has the signature: ‘a -> (‘a -> ‘b) -> ‘b. This is great for pipelining parameters, but it does not suit composition of functions in the absence of an input. Only the first of the following definitions is valid:

let quad x = x |> square |> square // valid

let quad = square |> square // invalid

This is where the compose operator (>>) is applicable.

The compose operator (>>)

Although it is possible to rewrite functions in a pipelined style, it can be more succinct to use the compose operator. Continuing the previous example:

let quad = square >> square

is valid and correct. The signature of >> is (‘a->’b)->(‘b->’c)->(‘a->’c).

The pipeline operator can be exchanged for the compose operator just by delaying the application of the left operand, for example:

x |> ( (f a) >> (g b c) >> h )

is equivalent to the earlier example with the pipeline operator.

The compose operator can be useful when you need to write a deferred function and you do not want to add the additional lambda expression:

(fun x -> x |> … ).

let delay x () = x

delay is a function that allows the value of a function to be deferred.  This is useful in function composition where functions expect to be composed with delayed behaviour, for example let f (xgen:unit->’x) = f (xgen()).  This approach to writing functions allows control of the timing of the production of the value of x and using delay allows a constant (or precomputed) value to be inserted.

let delayAny<‘T,’U> (x:’T) (_:’U) = x

Similar to delay, delayAny combines the semantics of ignore and absorbs the second curried argument.

[<RequiresExplicitTypeArgumentsAttribute>]
let delayNew<‘T when ‘T : (new : unit -> ‘T)> () = new ‘T()

delayNew is useful for allowing the delayed construction of an object.  This allows the object to be built at the correct location within a composed function.  Note that delayNew<AnyType> is a function and can be composed but delayNew<AnyType>() is a newly constructed value of type AnyType.

let chain<‘T> (f:’T->unit) (x:’T) = f x ; x

chain allows functions that consume their arguments to be used in pipelined or composed function chains.  As side effects are a part of working with non-functional programming paradigms, this can be useful to embed existing functionality into a composed function.

let ignoreAndChain<‘T,’I> (f:’T->’I) (x:’T) = f x |> ignore ; x

Similar to chain, ignoreAndChain allows the output of the existing function to be ignored.

let inject<‘T> (f:unit->unit) (x:’T) = f () ; x

inject is useful when a side effect that does not depend on the pipelined object needs to be embedded into the composed function.

let splitChain<‘T,’U> (f:unit->’T) (o:’U) = ( f(), o )

splitChain allows for the construction of a secondary value which can then be absorbed in a following step using the next function:

let joinChain<‘T,’U,’V> (f:’T->’U->’V) ((t,u):’T*’U) = f t u

joinChain applies the pair of arguments to the function, similar to ||>.

let reverseCurry2<‘A,’B,’C> (f:’A->’B->’C) =
fun (b:’B) -> fun (a:’A) -> f a b

reverseCurry2 takes a function with two curried arguments and swaps the order.

Notes

I will be using many of these functions in an upcoming series of posts on building a full WPF application in F# and I will discuss the code in more depth there.  I intend to show how these functions can allow F# code, that is similar to equivalent XAML, to be used to construct WPF XAML elements.

6 thoughts on “F#: Composing Functions”

1. Art Scott

Thanks Steve.
The F# WPF intersection is of great interest to me.

Art Scott

2. Andrew Garman

Excellent summary of and examples of composition and pipelines in F#!

There’s also the triple pipeline operators:
|||>
<|||

Also of note, F# type inference work best with forward pipelining and composition.

3. Shak

>In other words, the pipeline operator takes the left operand (in this case x) and applies it to the curried function given as its right operand.

Just for my own understanding, should that read “partially applied” instead of “curried”? As currying is an operation on functions (and all functions in F# are curried by default)?

1. Steve Horsfield Post author

Both are correct.
When using a pipeline operator, the function on the right must take at least one operand. Currying is the method of taking a tuple input function and translating it into a series of function generating lambdas. That function has a single argument, no longer a tuple, that leads to another function that also expects a single argument. This approach is the default for functional programming and leads to what we describe as partial application because we choose to disregard the additional internal functions, but each of those is a curried function because each input can be applied singularly.

The key here is that, usually, our function requires at least two arguments and these are supplied independently and not as a tuple. Only after we’ve applied one argument is it a partially applied function, but even this is not strictly required.