views:

289

answers:

4

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.

+8  A: 

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

Steve Haigh
@Steve: Thanks for the link, will look into that, a quick question - how does the pipelining know when to stop passing the information from one to the next? :) Thanks.
tommieb75
@Steve: That's a good link! Have this bookmarked once I finish Tomas Petricek's article! It's frying my brain at the moment with F# interactive shell here on my end....
tommieb75
Sorry, I'm just getting in to F# too so I'm not sure I can really answer your follow up question. I don't think you can "stop" the pipelining, I think all results from the first function get passed to the next one.
Steve Haigh
@Steve: Cool! Coming from a Assembler/C/C# background, it does fry the brain huh? :) Enjoy the learning of F#.... :) Thanks a million for your answer!
tommieb75
LOL, yeah, it's a bit of change of perspective thats for sure.
Steve Haigh
+7  A: 

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.
kvb
+2  A: 

Check out Pipelining in F# for an explanation.

(If you're familiar with the unix command line and pipes like

cat file1 | sort | head 

it's a similar idea; the result of the previous expression becomes the argument of the next function.)

Brian
+2  A: 

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
Tomas Petricek
It blows me away that the definition of such a powerful and useful operator is so trivial.
Benjol