首先- 是的,所有这些方式在“逻辑上”和编译到硬件时都是等效的。这是因为|>
and >>
运算符定义为inline
。定义大致如下:
let inline (|>) x f = f x
let inline (>>) f g = fun x -> g (f x)
的含义inline
关键字是编译器将以函数体替换对函数的调用,然后编译结果。因此,以下两者:
x |> f |> g
(f >> g) x
将被编译exactly与以下方式相同:
g (f x)
然而,在实践中,也存在一些问题。
其中一个问题是类型推断及其与类/接口的相互作用。考虑以下:
let b = "abcd" |> (fun x -> x.Length)
let a = (fun x -> x.Length) "abcd"
尽管这些定义在逻辑上和编译形式上都是等效的,但第一个定义将编译,而第二个定义则不会。发生这种情况是因为 F# 中的类型推断是从左到右进行的,没有双回,因此,在第一个定义中,当编译器到达x.Length
,它已经知道x
is a string
,因此它可以正确解析成员查找。在第二个例子中,编译器不知道什么x
是,因为它还没有遇到参数"abcd"
yet.
另一个问题与恐惧有关值限制 http://mlton.org/ValueRestriction。简单来说,它的定义是句法上(不符合逻辑!)value(与函数相反)不能是通用的。这有与可变性有关的模糊原因 - 请参阅链接的文章以获取解释。
将其应用于函数组合,请考虑以下代码(请注意,两者f
and g
是通用函数):
let f x = [x]
let g y = [y]
let h1 = f >> g
let h2 x = x |> f |> g
Here, h2
会编译得很好,但是h1
不会,抱怨价值限制。
在实践中,这三种方式之间的选择通常取决于可读性和便利性。这些本质上并不比其他更好。当我编写代码时,我通常根据自己的喜好进行选择。