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 |> … ).
Additional useful operators
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.
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.
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.