Method Chaining vs |> Pipe Operator

后端 未结 5 1821
爱一瞬间的悲伤
爱一瞬间的悲伤 2020-12-10 00:54

So I have the following code:

// Learn more about F# at http://fsharp.net
open System
open System.Linq
open Microsoft.         


        
相关标签:
5条回答
  • 2020-12-10 01:38

    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#.

    0 讨论(0)
  • 2020-12-10 01:39

    To my understanding, the F# |> operator was introduced to make sequence operations look like LINQ queries, or better to make it look similar to C# extension method chaining. List.map and filter, in fact, are functions in a "functional" way: get a sequence and a f as input, return a sequence. Without pipe, the F# variant will be

    filter(fun(x) -> x > 10, map(fun(x) -> x*3, a))
    

    Notice that visually the order of the functions is reversed (application is still in the same order) : with |> they look more "natural", or better, they look more similar to the C# variant. C# achieves the same goal through extension methods: remember that the C# one is actually

    Enumerable.Where(Enumerable.Select(a, f1), f2)
    

    Enumerable.Select is a function where the first parameter is a "this IEnumerable", which is used by the compiler to transform it to a.Select... In the end, they are language facilities (realized through a compiler transformations in C#, and using operators and partial application in F#) to make nested function calls look more like a chain of transformations.

    0 讨论(0)
  • 2020-12-10 01:51

    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

    0 讨论(0)
  • 2020-12-10 01:58

    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)
    
    0 讨论(0)
  • 2020-12-10 01:58

    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)
    
    0 讨论(0)
提交回复
热议问题