Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

To Pipe or Not to Pipe?

Tags:

f#

That is (sort of) the question.

I'm trying to learn F# more lately and my resources seem to prefer piping in places where it's not clear a benefit exists. For example, given a function in curried form:

f: a -> b -> c -> R

I can call it by supplying all the arguments inline or pipe c in like so:

let r = f a b c 
let r = c |> f a b

But is there a cost or benefit or is it purely a stylistic preference?

like image 916
ChiefTwoPencils Avatar asked Jan 01 '23 12:01

ChiefTwoPencils


1 Answers

There should be no cost.

UPDATE

There may be some extra cost. Sometimes an extra call is inserted, but some other times the compiler can optimize it avoiding the extra call.

I tested the following code:

let concat (a:string) (b:string) = System.String.Concat(a, b)

let f1 g a c = g a c
let f2 g a c = c |> g a

f1     concat "b" "c" \\ 00:00:33.8895616
f2     concat "b" "c" \\ 00:00:34.6051700
       concat "b" "c" \\ 00:00:35.0669532
"c" |> concat "b"     \\ 00:00:35.1948296
f1     (+)    "b" "c" \\ 00:00:35.4796687
f2     (+)    "b" "c" \\ 00:00:49.3227193
       (+)    "b" "c" \\ 00:00:35.0689207
"c" |> (+)    "b"     \\ 00:00:35.8214733

For each case I performed a billion calls (1_000_000_000) and those were the times. As you can see only calling f2 (+) "b" "c" was slower than the rest which may have something to do with (+) being an operator that uses SRTP.

Thanks to @PhillipCarter for clarifying.


The use of pipe helps with type inference:

let words = "many words".Split ' '

let wordsU1 = words |> Array.map (fun w -> w.ToUpper())  // ok
wordsU1 |> Seq.iter (printfn "words are %A") 

let wordsU2 = Array.map (fun w -> w.ToUpper()) words     // type annotation needed: (w:string)
Seq.iter (printfn "words are %A") wordsU2

The two calls to Array.map are equivalent but the second one complains that it does not know the type of w. That is because F# type inference mechanism works from left to right and in the second case words is after the lambda function.

It is also better stylistically. The code above can be better expressed like this:

"many words".Split ' '
|> Array.map (fun w -> w.ToUpper())
|> Seq.iter (printfn "words are %A")

So the pipe can be used to chain several expressions without having to use parenthesis or binding it to names.

like image 86
AMieres Avatar answered Jan 09 '23 23:01

AMieres