F# 管道运算符混淆

Gra*_*ler 2 f#

我正在学习 F#,|>、>> 和 << 运算符的用例让我感到困惑。我知道所有 if 语句、函数等都像变量一样,但它们是如何工作的呢?

Dav*_*aab 5

通常我们(社区)说 Pipe Operator|>只是一种在函数调用之前写入函数的最后一个参数的方法。例如

f x y
Run Code Online (Sandbox Code Playgroud)

可以写成

y |> f x
Run Code Online (Sandbox Code Playgroud)

但为了正确性,事实并非如此。它只是将下一个参数传递给函数。所以你甚至可以写。

y |> (x |> f)
Run Code Online (Sandbox Code Playgroud)

所有这些以及所有其他类型的运算符都可以工作,因为在 F# 中所有函数都默认被柯里化。这意味着,只存在带有一个参数的函数。具有多个参数的函数是通过一个函数返回另一个函数来实现的。

你也可以写

(f x) y
Run Code Online (Sandbox Code Playgroud)

例如。该函数f是以另一个函数x为参数并返回另一个函数的函数。然后,这将y作为参数传递。

这个过程是由语言自动完成的。所以如果你写

let f x y z = x + y + z
Run Code Online (Sandbox Code Playgroud)

它等同于:

let f = fun x -> fun y -> fun z -> x + y + z
Run Code Online (Sandbox Code Playgroud)

顺便说一句,与类似 LISP 的语言相比,柯里化是类似 ML 的语言中不强制执行括号的原因。否则你需要写:

(((f 1) 2) 3)
Run Code Online (Sandbox Code Playgroud)

执行f带有三个参数的函数。

管道运算符本身只是另一个函数,它定义为

let (|>) x f = f x
Run Code Online (Sandbox Code Playgroud)

它采用一个值x作为其第一个参数。一个函数f作为它的第二个参数。因为运算符是书面的“中缀”(这意味着两个操作数之间)而不是“前缀”(在参数之前,正常方式),这意味着运算符的左侧参数是第一个参数。

在我看来,|>大多数 F# 人用得太多了。如果您有一系列操作,一个接一个,那么使用管道是有意义的。通常,例如,如果您有多个列表操作。

假设您想要对列表中的所有数字进行平方,然后仅过滤偶数。没有管道你就可以写。

List.filter isEven (List.map square [1..10])
Run Code Online (Sandbox Code Playgroud)

这里的第二个参数List.filter是返回的列表List.map。您也可以将其写为

List.map square [1..10]
|> List.filter isEven
Run Code Online (Sandbox Code Playgroud)

管道是函数应用程序,这意味着,您将执行/运行一个函数,因此它会计算并返回一个值作为其结果。

上面的例子中List.map首先执行,并将结果传递给List.filter. 对于有管道和没有管道的情况都是如此。但有时,您想要创建另一个函数,而不是执行/运行函数。假设您想根据上面的内容创建一个函数。您可以编写的两个版本是

let evenSquares xs = List.filter isEven (List.map square xs)
let evenSquares xs = List.map square xs |> List.filter isEven
Run Code Online (Sandbox Code Playgroud)

您也可以将其写为函数组合。

let evenSquares = List.filter isEven << List.map square
let evenSquares = List.map square >> List.filter isEven
Run Code Online (Sandbox Code Playgroud)

<<运算符类似于“正常”方式的函数组合,即如何编写带括号的函数。以及>>“向后”组合,如何用|>.

F# 文档以另一种方式编写它,即向后和向前。但我认为 F# 语言的创建者错了。

函数组合运算符定义为:

let (<<) f g x = f (g x)
let (>>) f g x = g (f x)
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,从技术上讲,该运算符具有三个参数。但请记住柯里化。当您编写 时f << g,结果是另一个函数,它需要最后一个参数x。传递比需要的更少的参数通常也称为部分应用

函数组合在 F# 中较少使用,因为如果函数参数是泛型,编译器有时会出现类型推断问题。

理论上,您可以在不定义变量的情况下编写程序,只需通过函数组合即可。这也称为Point-Free 风格

我不会推荐它,它通常会使代码更难阅读和/或理解。但如果您想将一个函数传递给另一个 高阶函数,有时会使用它。这意味着,一个函数接受另一个函数作为参数。诸如此类List.mapList.filter