Friday, May 14, 2010

F#: Piping and Composition

Piping and compositions are very expressive tools in F#. It significantly improves code readability.

Let’s have the following functions:

> let square x = x*x;;
val square : int -> int

> let negate x = -x;;
val negate : int –> int

Function square returns the square of x, while negate returns the negated value of x.

We can calculate square(negate(10)) as follows:

> let x = negate 10;;
val x : int = –10

> let x = square x;;
val x : int = 100

Or a simpler way:

> let x = square (negate 10);;
val x : int = 100


We can define piping as symbol-named function definition that simply applies two functions in reverse order:

let (|>) f x =
  x f

The example with piping:

> 10 |> negate |> square;;
val it : int = 100

That means ((10 negate) square) –> (square (negate 10)). I used an integer (10) as f.


Composition is a reverse-order function composition:

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

The example with composition:

> (negate >> square) 10;;
val it : int = 100

That means ((negate square) 10) –> (square (negate 10)).


The examples above are very simple ones. The real benefits of piping and composition reveal in case of function definitions and usage.

Pipes can reverse parameter definition orders (like in 10 |> negate) and can help type inferring. When F# infers types, it evaluates from upper left corner to lover right corner of the text file, so sometimes it doesn't know the type of a function parameter. Pipe brings parameters forward.

Composition can help omitting function parameter declaration. If the first function expects a parameter, the composed function expects it’s parameter.