Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In F#, what does pipelining mean?

I was reading this article by Tomas Petricek, and it mentioned the pipelining |> as in the example given:

> let nums = [1; 2; 3; 4; 5];;
val nums : list<int>

> let odds_plus_ten = 
        nums
        |> List.filter (fun n-> n%2 <> 0)
        |> List.map (add 10)
val odds_plus_ten : list<int> = [11; 13; 15];;

What does pipelining mean? Initially, I thought it was akin to a CPU instruction being pipelined within the cores. Can you explain what it is and how does it work in the context of F#?

Thanks, Best regards, Tom.

like image 233
t0mm13b Avatar asked Feb 01 '10 13:02

t0mm13b


3 Answers

In some ways there's nothing special about pipelining; instead of writing f (g (h x)), you can write x |> h |> g |> f, which doesn't seem like an obvious improvement. However, there are two points worth keeping in mind:

  1. Sometimes the reading order is better for the pipelined version: "Take x and send it to h, send the result to g, send the result to f" can be easier to understand than "Apply f to the result of applying g to the result of applying h to x".
  2. Type inference often works much better for the pipelined version. This is probably the biggest reason that pipelining is used so much in F#. Since type inference proceeds left to right, x |> Array.map (fun s -> s.Length) will work when x is a string[], but Array.map (fun s -> s.Length) x won't; you need to do Array.map (fun (s:string) -> s.Length) x instead.
like image 159
kvb Avatar answered Oct 16 '22 16:10

kvb


Pipelining means passing the results of one function to another function. In the example you give "nums" is passed the List.Filter, the filtered results are then passed to List.Map.

More info here: http://msdn.microsoft.com/en-us/magazine/cc164244.aspx#S6

like image 12
Steve Avatar answered Oct 16 '22 16:10

Steve


As others mentioned, pipelining is more like a UNIX shell pipeline. It let's you write some input followed by operations that should be applied to it, instead of usual nested function calls. In this example, the standard F# code would look like this:

let r = List.map (add 10) (List.filter (fun n-> n%2 <> 0) nums)

Note that the input nums is deeply nested in the expression and it isn't easy to see that it is first filtered and then projected. Using pipelining, you can write the code differently, but it will mean exactly the same thing.

The trick is that pipelining operator takes two parameters using infix notation (e.g. x |> f). The x parameter will be passed as the last argument to a function on the right (f). You can use pipelining with any F# functions:

let sinOne = 1.0 |> sin

let add a b = a + b
let r = 10 |> add 5 // it doesn't always make code more readable :-)

An important point about F# pipelining operator is that it isn't any special built-in feature of the language. It is a simple custom operator that you can define on your own:

let (|>) x f = f x

// Thanks to operator associativity rules, the following:
let r = 1.0 |> sin |> sqrt
// ...means this:
let r = (1.0 |> sin) |> sqrt
like image 3
Tomas Petricek Avatar answered Oct 16 '22 17:10

Tomas Petricek