So I have the following code:
// Learn more about F# at http://fsharp.net
open System
open System.Linq
open Microsoft.FSharp.Collections
let a = [1; 2; 3; 4; 54; 9]
let c = a |> List.map(fun(x) -> x*3) |> List.filter(fun(x) -> x > 10)
let d = a.Select(fun(x) -> x*3).Where(fun(x) -> x > 10)
for i in c do
Console.WriteLine(i)
for i in d do
Console.WriteLine(i)
Both seem to do the same thing, but most F# examples I see use the |> pipe operator, while I'm more used to method chaining (a.l.a. C# Linq). The latter is also somewhat shorter, albeit somewhat more crunched together. For now i'm using the C# Linq syntax, but that's more of habit/inertia rather than any real design decision.
Are there any considerations that I should know about, or are they basically identical?
Edit: The other consideration is that the Pipe syntax is significantly more "noisy" than the Linq syntax: the operation I am doing (e.g. "map") is really short and in lowercase, while each one is preceded by this Huge "|> List" that, apart from making it longer distracts the eye away from the tiny, lowercase method name. Even StackOverflow's syntax highlighter highlights the wrong (irrelevant) thing. Either that or I'm just not used to it.
Method chaining is a technique that is used for making multiple method calls on the same object, using the object reference just once. Example − Assume we have a class Foo that has two methods, bar and baz. We create an instance of the class Foo − foo = Foo()
The tee ( %T>% ) operator allows you to continue piping functions that normally cause termination. The compound assignment %<>% operator is used to update a value by first piping it into one or more expressions, and then assigning the result.
The JavaScript Pipeline Operator ( |> ) is used to pipe the value of an expression into a function. This operator makes chained functions more readable. This function is called using ( |> ) operator and whatever value is used on the pipeline operator is passed as an argument to the function.
Pipes are an extremely useful tool from the magrittr package 1 that allow you to express a sequence of multiple operations. They can greatly simplify your code and make your operations more intuitive.
Pipelining supports F#'s left-to-right type inference. a.GroupBy
requires that the type of a
is already known to be seq<_>
, whereas a |> Seq.groupBy
itself infers a
to be seq<_>
. The following function:
let increment items = items |> Seq.map (fun i -> i + 1)
requires a type annotation to be written using LINQ:
let increment (items:seq<_>) = items.Select(fun x -> x + 1)
As you get comfortable with the functional style you'll find ways to make your code more concise. For example, the previous function can be shortened to:
let increment = Seq.map ((+) 1)
Others already explained most of the differences between the two styles. In my point of view, the most important is type inference (mentioned by Daniel) which works more nicely with the idiomatic F# style based on pipelining and functions like List.map
.
Another difference is that when using the F# style, you can more easily see which part of the computation evaluates lazily, when the evaluation is forced etc., because you can combine functions for IEnumerable<_>
(called Seq
) and functions for lists or arrays:
let foo input =
input
|> Array.map (fun a -> a) // Takes array and returns array (more efficient)
|> Seq.windowed 2 // Create lazy sliding window
|> Seq.take 10 // Take sequence of first 10 elements
|> Array.ofSeq // Convert back to array
I also find the |>
operator more syntactically convenient, because I never know how to correctly indent code that uses .Foo
- especially where to place the dot. On the other hand, |>
has quite established coding style in F#.
In general, I recommend using the |>
style because it is "more standard". There is nothing wrong with using the C# style in F#, but you may find that writing code in a more idiomatic style makes it easier to use some interesting functional programming concepts that work better in F# than in C#.
Actually the pipe operator does nothing but swap the function and argument around, to my knowledge there's no difference between f1 (f2 3)
and 3 |> f2 |> f1
besides that the latter is easier to read when you're chaining a lot together.
edit: it's actually defined just as that: let inline (|>) x f = f x
.
I guess the reason you tend to see the List.map approach more than Linq is because in OCaml (the predecessor to F#), these operators have always been there, so this style of coding is really entrenched in the way functional programmers think. A List is a very basic concept in F#, it is slightly different from a IEnumerable (that's closer to a Seq).
Linq is largely an undertaking to bring these functional programming concepts to C# and VB. So they're in the .Net platform and therefor available, but in F# they're kind of redundant.
Also List.map is a very simple operation, whereas the Linq approach brings in the whole framework with lazy evaluation etc that brings some overhead. But I don't think that will make a significant difference until you really use it a lot. I heard in some talk that the reason the C# compiler doesn't use Linq more is because of this reason, but in normal life you're not likely to notice.
So all in all, do what you feel best with, there's no right or wrong. Personally I would go with the List operators because they're more standard in 'idiomatic' F#.
GJ
Well one thing that you will probably run into eventually is problems with type inference. Look at this example for instance:
open System
open System.Linq
open Microsoft.FSharp.Collections
let a = ["a", 2; "b", 1; "a", 42; ]
let c = a |> Seq.groupBy (fst) |> Seq.map (fun (x,y) -> x, Seq.length y)
//Type inference will not work here
//let d1 = a.GroupBy(fun x -> fst x).Select(fun x -> x.Key, x.Count())
//So we need this instead
let d2 = a.GroupBy(fun x -> fst x).Select(fun (x : IGrouping<string, (string * int)>) -> x.Key, x.Count())
for i in c do
Console.WriteLine(i)
for i in d2 do
Console.WriteLine(i)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With