F# 管道运算符混淆

  • 本文关键字:运算符 管道 f#
  • 更新时间 :
  • 英文 :


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

通常我们(社区)说管道运算符|>只是一种方法,在函数调用之前编写函数的最后一个参数。例如

f x y

可以写

y |> f x

但为了正确起见,这不是真的。它只是将下一个参数传递给函数。所以你甚至可以写。

y |> (x |> f)

所有这些以及所有其他类型的运算符都有效,因为在 F# 中,默认情况下所有函数都是柯里式的。这意味着,只存在具有一个参数的函数。具有许多参数的函数被实现为一个函数返回另一个函数。

你也可以写

(f x) y

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

此过程由语言自动完成。所以如果你写

let f x y z = x + y + z

它与:

let f = fun x -> fun y -> fun z -> x + y + z

顺便说一下,与类似 LISP 的语言相比,类 ML 语言中的括号没有强制执行的原因。否则,您需要编写:

(((f 1) 2) 3)

执行具有三个参数的函数f

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

let (|>) x f = f x

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

在我看来,大多数 F# 人都过多地使用了|>。如果您有一个接一个的操作链,则使用管道是有意义的。例如,如果您有多个列表操作,则通常如此。

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

List.filter isEven (List.map square [1..10])

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

List.map square [1..10]
|> List.filter isEven

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

在上面的示例中,首先执行List.map,并将结果传递给List.filter。管道和不管道都是如此。但有时,您想创建另一个函数,而不是执行/运行函数。假设您想从上面创建一个函数。您可以编写的两个版本是

let evenSquares xs = List.filter isEven (List.map square xs)
let evenSquares xs = List.map square xs |> List.filter isEven

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

let evenSquares = List.filter isEven << List.map square
let evenSquares = List.map square >> List.filter isEven

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

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

函数组合运算符定义为:

let (<<) f g x = f (g x)
let (>>) f g x = g (f x)

如您所见,运算符在技术上有三个参数。但请记住咖喱。当你写f << g时,结果是另一个函数,它期望最后一个参数x。传递较少的参数通常也称为部分应用程序。

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

从理论上讲,你可以在不定义变量的情况下编写程序,只需通过函数组合即可。这也称为无点样式。

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

管道和组合运算符的定义很简单,但很难掌握。但是一旦我们理解了它们,它们就非常有用,当我们回到 C# 时,我们会想念它们。

这里有一些解释,但您可以从自己的实验中获得最佳反馈。玩得愉快!

管道右侧操作员|>

val |> fnfn val

效用:

  • 构建管道,将调用链接到函数:x |> f |> gg (f x)
    • 更易于阅读:只需遵循数据流
    • 无中间变量
  • 英语自然语言:主语动词。
    • 它在面向对象的代码中是常规的:myObject.do()
    • 在 F# 中,"主题"通常是最后一个参数:List.map f list。使用|>,我们得到自然的"主语动词"顺序:list |> List.map f
  • 最终好处但并非最不重要的一点是:帮助类型推理:
let items = ["a"; "bb"; "ccc"]
let longestKo = List.maxBy (fun x -> x.Length) items  // ❌ Error FS0072
//                                   ~~~~~~~~
let longest = items |> List.maxBy (fun x -> x.Length) // ✅ return "ccc"

管道左操作员<|

fn <| expressionfn (expression)

  • 使用量少于|>
  • ✅ 小好处:避免使用括号
  • ❌ 主要缺点:英语自然"从左到右"阅读顺序的反比和执行顺序的反转(因为左结合性)
printf "%i" 1+2          //    Error
printf "%i" (1+2)        // With parentheses
printf "%i" <| 1+2       // With pipe left

这种表达方式呢:x |> fn <| y

  • 理论上,允许在中缀位置使用fn,相当于fn x y
  • 在实践中,对于
  • 一些不习惯它的读者来说,这可能会非常混乱。

最好避免使用<|

正向组合运算符>>

放置在 2 个函数之间的二元运算符:
f >> gfun x -> g (f x)fun x -> x |> f |> g

第一个函数的结果用作第二个函数
的参数→类型必须匹配:f: 'T -> 'Ug: 'U -> 'Vf >> g :'T -> 'V

let add1 x = x + 1
let times2 x = x * 2
let add1Times2 x = times2(add1 x) //    Style explicit but heavy
let add1Times2' = add1 >> times2  //    Style concise

向后组合运算符<<

f >> gg << f

>>少用,除了按英文顺序获取术语:

let even x = x % 2 = 0
// even not   
let odd x = x |> even |> not
// "not even" is easier to read   
let odd = not << even

注:<<是数学函数组成g ∘ ffun x -> g (f x)g << f
这在 F# 中令人困惑,因为它>>通常称为"组合运算符">(通常省略"转发")。

另一方面,用于这些运算符的符号对于记住函数的执行顺序非常有用:f >> g表示应用f然后应用g。即使参数是隐式的,我们也得到数据流方向:

  • >>: 从左到右→f >> gfun x -> x |> f |> g
  • <<: 从右到左→f << gfun x -> f <| (g <| x)

(根据大卫的好建议编辑)

相关内容

  • 没有找到相关文章

最新更新