如何在F#中定义三元内联运算符|>>及通用运算符|>>*?
F# 自定义中缀运算符
|>>的优先级问题与解决方案 需求概述
- 定义中缀运算符
|>>,使i |>> f1 f2等价于(f1 i, f2 i),将参数传递给两个函数并返回结果元组 - 定义通用绑定运算符
|>>*,使i (|>>* b) f1 f2等价于b (f1 i) (f2 i),支持自定义结果绑定逻辑
现有尝试代码
let (|>>*) (i: 'IN) (b : 'S -> 'T -> 'OUT) = fun (f1: 'IN -> 'S) (f2: 'IN -> 'T) -> b (f1 i) (f2 i) let (|>>) i f1 = (|>>*) i (fun a b -> a,b) f1 let x = 10 |>> (fun x -> x + 2) (fun x -> x + 2)
问题分析
直接调用10 |>> f1 f2会触发类型解析错误,必须写成(10 |>> f1) f2才能得到正确结果。根源在于F#中函数调用(空格分隔)的优先级远高于自定义中缀运算符:
- 函数应用的优先级为10(最高级别),所有自定义中缀运算符默认优先级为4,最高仅能设置为9,无法超过函数应用的优先级
- 因此
10 |>> f1 f2会被解析为10 |>> (f1 f2),即把f2作为参数传给f1,而非预期的将f1和f2依次传递给|>>的返回函数,导致类型不匹配
解决方案
方案1:调整运算符定义,直接接受元组形式的函数对
这种方案类型安全,语法直观,仅需给函数对添加括号:
// 通用绑定运算符 |>>* 改为直接接受两个函数参数 let (|>>*) (i: 'IN) (b : 'S -> 'T -> 'OUT) (f1: 'IN -> 'S) (f2: 'IN -> 'T) = b (f1 i) (f2 i) // 元组版本的 |>> 运算符 let (|>>) i (f1, f2) = (|>>*) i (fun a b -> a, b) f1 f2 // 调用示例 let x = 10 |>> (fun x -> x + 2, fun x -> x - 1) // 返回 (12, 9)
方案2:保持柯里化设计,使用括号或管道符明确调用顺序
如果坚持原有的柯里化逻辑,需通过括号或管道符强制指定解析顺序:
// 保留原运算符定义,添加优先级和结合性属性优化解析 [<OperatorPrecedence(9)>] [<RightAssociative>] let (|>>*) (i: 'IN) (b : 'S -> 'T -> 'OUT) = fun (f1: 'IN -> 'S) (f2: 'IN -> 'T) -> b (f1 i) (f2 i) [<OperatorPrecedence(9)>] [<RightAssociative>] let (|>>) i f1 = (|>>*) i (fun a b -> a,b) f1 // 调用方式1:添加括号明确顺序 let x1 = (10 |>> (fun x -> x + 2)) (fun x -> x - 1) // 返回 (12, 9) // 调用方式2:使用管道符传递第二个函数 let x2 = 10 |>> (fun x -> x + 2) |> (fun f -> f (fun x -> x - 1))
方案3:新增组合运算符,接近预期语法
通过新增一个函数组合运算符&&&,实现无需括号的调用语法:
// 函数组合运算符:将两个函数组合为返回元组的函数 let (&&&) f1 f2 = fun i -> (f1 i, f2 i) // 管道运算符:将参数传递给组合后的函数 let (|>>) i f = f i // 调用示例:语法接近预期的 `i |>> f1 f2` let x = 10 |>> (fun x -> x + 2) &&& (fun x -> x - 1) // 返回 (12, 9)
关键结论
F#的语法规则决定了函数应用的优先级无法被任何运算符超越,因此无法直接实现i |>> f1 f2的解析逻辑。上述方案可根据实际需求选择,其中方案3的语法最接近预期,方案1的类型安全性最优。
内容的提问来源于stack exchange,提问作者ChomskyEnjoyer
相关产品推荐
相关产品推荐

