方法链接vs |>管道操作员

时间:2011-10-08 16:19:17

标签: map filter f# method-chaining c#-to-f#

所以我有以下代码:

// 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)

两者似乎做同样的事情,但我看到的大多数F#例子都使用了|>管道运算符,而我更习惯于方法链接(a.l.a. C#Linq)。后者也稍微短一些,虽然稍微嘎吱作响。现在我正在使用C#Linq语法,但这更多是习惯/惯性,而不是任何真正的设计决策。

我是否应该知道任何考虑因素,或者它们基本相同?

编辑: 另一个考虑因素是管道语法比Linq语法明显更“嘈杂”:我正在进行的操作(例如“map”)非常简短且小写,而每个操作前面都有这个巨大的“|>列表“除了让它更长时间,它会使眼睛远离微小的小写方法名称。甚至StackOverflow的语法高亮显示也突出了错误(不相关)的事情。无论是那个还是我都不习惯它。

5 个答案:

答案 0 :(得分:22)

Pipelining支持F#的从左到右类型推断。 a.GroupBy要求已知a的类型为seq<_>,而a |> Seq.groupBy本身则推断aseq<_>。以下功能:

let increment items = items |> Seq.map (fun i -> i + 1)

需要使用LINQ编写类型注释:

let increment (items:seq<_>) = items.Select(fun x -> x + 1)

随着您对功能样式的熟悉,您将找到使代码更简洁的方法。例如,之前的功能可以缩短为:

let increment = Seq.map ((+) 1)

答案 1 :(得分:14)

其他人已经解释了两种风格之间的大部分差异。在我看来,最重要的是类型推断(Daniel提到),它更适用于基于流水线和List.map等函数的惯用F#风格。

另一个不同之处在于,当使用F#样式时,您可以更容易地看到计算的哪个部分懒惰地评估,何时强制评估等,因为您可以组合IEnumerable<_>的函数(称为{{1列表或数组的函数和函数:

Seq

我还发现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 运算符在语法上更方便,因为我永远不知道如何正确缩进使用|>的代码 - 尤其是放置点的位置。另一方面,.Foo在F#中具有相当完善的编码风格。

一般情况下,我建议使用|>样式,因为它更“标准”。在F#中使用C#样式没有任何问题,但您可能会发现以更惯用的方式编写代码可以更容易地使用一些在F#中比在C#中更好的函数式编程概念。

答案 2 :(得分:8)

实际上管道操作员除了交换函数和参数之外什么都不做,据我所知,f1 (f2 3)3 |> f2 |> f1之间没有区别,除了后者在链接很多时更容易阅读在一起。

编辑:它是actually defined,就像那样:let inline (|>) x f = f x

我猜你倾向于看到List.map方法比Linq更多的原因是因为在OCaml(F#的前身)中,这些运算符一直存在,所以这种编码风格确实在功能程序员的方式中根深蒂固认为。 List是F#中一个非常基本的概念,它与IEnumerable(更接近Seq)略有不同。

Linq主要是将这些函数式编程概念引入C#和VB。所以他们在.Net平台上,因此可用,但在F#中他们是多余的。

List.map也是一个非常简单的操作,而Linq方法带来了整个框架的懒惰评估等带来一些开销。 但我不认为这会产生重大影响,直到你真正使用它为止。我在一些谈话中听说C#编译器不使用Linq的原因是因为这个原因,但在正常生活中你不太可能注意到。

总而言之,做你最满意的事,没有对错。我个人会选择List运算符,因为它们在'惯用'F#中更为标准。

GJ

答案 3 :(得分:3)

最终你可能遇到的一件事是类型推断的问题。例如,请查看此示例:

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)

答案 4 :(得分:2)

根据我的理解,F#|&gt;引入运算符使序列操作看起来像LINQ查询,或者更好地使它看起来类似于C#扩展方法链接。 事实上,List.map和过滤器是以“功能”方式的函数:获取序列和f作为输入,返回序列。没有管道,F#变体将是

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

请注意,视觉上功能的顺序是相反的(应用程序仍然是相同的顺序) :with |&gt;它们看起来更“自然”,或更好,它们看起来更像C#变体。 C#通过扩展方法实现了相同的目标:记住C#one实际上是

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

Enumerable.Select是一个函数,其中第一个参数是“this IEnumerable”,编译器使用它将其转换为a.Select ... 最后,它们是语言工具(通过C#中的编译器转换实现,并使用F#中的运算符和部分应用程序实现)使嵌套函数调用看起来更像是一个转换链。