F#运算符定义规则总结

开发 后端
在这里我们将总结F#运算符定义规则,希望通过本文,能为大家打开一扇通往F#的窗口。

本文将讨论的是F#运算符相关定义规则的问题,这些规则覆盖大多数开发场景。希望通过本文能对大家了解F#运算符规则有所帮助。

#T#

F#允许开发人员定义或重载各类运算符,合理利用这一规则可以让编程变得方便,让代码更容易阅读。例如,在使用F#的MailboxProcessor的时候,我会习惯于定义一个运算符来代替显式的Post操作:

  1. let (>>) m (agent: MailboxProcessor<_>) = agent.Post m 

这样便可以这样发送消息:

  1. let agent = MailboxProcessor.Start(fun o -> async { o |> ignore });  
  2. "hello world" >> agent 

不过,F#的运算符定义规则较为复杂,要搞清楚编译器的整体处理方式着实花费了一番功夫。比较奇怪的是,即便是《Expert F#》中对于这个话题也没有详细的叙述——更夸张的是MSDN的文档也相当马虎,甚至有代码缺失以及与试验不符情况(因为还没有正式发布?)。于是我连看带试,最终打算总结一番,作为备忘的同时也算是补充互联网资源。

运算符重载

F#中允许在global级别重载一个运算符,甚至“覆盖”原有的定义。例如,我们可以写一个Operator模块,其中只有一个“加号”的定义:

  1. // operator.fs  
  2. #light  
  3. module Operator  
  4. let (+) (a:int) (b:int) = a * b 

我们可以在另一个模块中引入Operator模块,于是两个整数的“加法”便可以得出乘法的效果了:

  1. 1 + 2 |> printfn "%i" // 2 

从中也可以看出,胡乱重载运算符实在是一种没事找事的方式。因此,现在这篇文章纯粹都是在“谈技术”,所有的内容,包括示例都不代表“***实践”。

运算符的组成

在F#中,自定义运算符可以由以下字符组成:

  1. ! % & * + - . / < = > ? @ ^ | ~ 

目前在MSDN中,《Operator Overloading (F#)》一文写到“$”也可以作为运算符的组成,不过***的F#编译器(v1.9.7.4)中会对此作出“警告”,表示以后它将成为一个F#的保留字,不允许用作运算符。

在F#中,每个运算符不限长度。也就是说,如果您喜欢的话,完全可以定义这样的一个运算符来表示整数加法:

  1. let (!%&*+-./<=>?@^|~!%&*+-./<=>?@^|~) (x : int) (y : int) = x * y 

F#会将运算符编译为程序集中具体的方法,其命名遵循一定规则。不过在使用时我们并不需要关心这些。如果您对这方面的具体信息感兴趣,可以参考MSDN中《Operator Overloading (F#)》一文。

前缀与中缀运算符

前缀(prefix)运算符,表示运算符出现在运算数之前,例如“负号”便是个前缀运算符:

  1. let a = 1  
  2. -a |> printfn "%i" // -1 

中缀(postfix)运算符,表示运算符出现在两个运算数之间,例如最常见的“加法”便是个中缀运算符:

  1. 1 + 2 |> printfn "%i" // 3 

在自定义运算符时,F#并不允许我们指定某个运算符是前缀还是中缀运算符,编译器会自动根据运算符的“首字母”来决定它是前缀还是中缀的。例如,首字母为“感叹号”的运算符便是“前缀”运算符:

  1. let (!+) (x:int) (y:int) = x + y 

根据这个规则,我们只能将“!+”作为前缀运算符来使用:

  1. 1 (!+) 2 |> printfn "%i" // 编译失败!  
  2. !+ (!+ 1 2) 3 |> printfn "%i" // 6 

关于某个字母表示前缀还是中缀运算符,您可以参考《Operator Overloading (F#)》一文中的表格。可以发现,大部分运算符都是中缀的,而只有少数是前缀运算符。至于后缀运算符……F#并不支持后缀运算符。

F#运算符的优先级

每个运算符有其自己的优先级(precedence),优先级表示一个表达式中连续出现多个运算符时,究竟哪个运算符先生效。例如,我们都知道“先乘除后加减”:

3 + 4 * 5 |> printfn "%i" // 23那么,我们自定义的运算符优先级又如何呢?F#同样是通过运算符的首字母来决定它的优先级的,关于不同首字母的优先级高低,可以参考MSDN中《Symbol and Operator Reference (F#)》的Operator Precedence一节,它按照优先级从低到高列举所有的运算符。

例如“除号”的优先级比“加号”高,因此:

  1. let (+/) (x:int) (y : int) = x / y  
  2. let (/+) (x:int) (y : int) = x + y  
  3. 4 + 4 / 2 |> printfn "%i" // 6  
  4. 4 /+ 4 +/ 2 |> printfn "%i" // 4 

值得注意的是,如果两个运算符的首字母相同,则F#便认为两个运算符的优先级相同,而不在比较它们后续字符的优先级高低。不过在优先级的判定中有个特例,那就是“点”,它并不参与优先级的比较中,此时便以后面的字符为准了:

  1. let (.+) (x:int) (y:int) = x + y  
  2. let (..*) (x:int) (y:int) = x * y  
  3. // 仍然是“先乘除后加减”  
  4. 3 .+ 4 ..* 5 |> printfn "%i" // 23  
  5. 3 ..* 4 .+ 5 |> printfn "%i" // 17 

当然,括号可以改变运算符的优先级,这点再正常不过了。还有一点,便是“转发”操作(即本文代码中出现的“|>”),它以“|”作为首字母。根据规则,它的优先级是很低的(在自定义运算符中是***的)。因此,无论我们左侧的表达式中使用了什么样的运算符,都是***才进行“转发”操作。

运算符的相关性

每个运算符都有其相关性(associativity)。相关性的作用是,一旦一个表达式中连续出现优先级相同的运算符,那么它们究竟是从左向右计算(左相关),还是从右向左计算(右相关)。
例如,最普通的“除号”便是左相关的:

  1. 4 / 2 / 2 |> printfn "%i" // 1 

而List操作的“连接符”(连接单个元素与一个列表)便是右相关的:

  1. 1 :: 2 :: 3 :: [] |> printfn "%A" // [1; 2; 3] 

在F#中,运算符的相关性也是由首字母决定的,您可以在MSDN中《Symbol and Operator Reference (F#)》的Operator Precedence一节查到所有字符的相关性。

例如,“大于号”是左相关的,因此:

  1. let (>+) (x:int) (y:int) = x + y  
  2. let (>*) (x:int) (y:int) = x * y  
  3. 3 >+ 4 >* 5 |> printfn "%i" // 35  
  4. 3 >* 4 >+ 5 |> printfn "%i" // 17而“^”是右相关的:  
  5. let (^+) (x:int) (y:int) = x + y  
  6. let (^*) (x:int) (y:int) = x * y  
  7. 3 ^+ 4 ^* 5 |> printfn "%i" // 23  
  8. 3 ^* 4 ^+ 5 |> printfn "%i" // 27 

自然,括号可以改变运算符的相关性。

一元运算符

之前我们讨论的大都是二元运算符(即需要两个运算数),不过有一个字符比较特殊,它便是“~”,我们可以利用它来定义一个“一元运算符”:

  1. let (~-) (x:int) = x + 1  
  2. let a = 1  
  3. -a |> printfn "%i" // 2 

这效果是不是很神奇?因此,如果您要重载现有的运算符,请一定三思而后行。

为类型定义运算符

之前我们一直在讨论“全局”级别的运算符。事实上,运算符也可以定义在某个类型内部。例如:

  1. // 定义  
  2. type Rational(numer, denom) =  
  3.  
  4.     member r.Numer = numer  
  5.     member r.Denom = denom  
  6.  
  7.     static member (-) (x:Rational, y:Rational) =  
  8.         let n = x.Numer * y.Denom - y.Numer * x.Denom  
  9.         let d = x.Denom * y.Denom  
  10.         new Rational(n, d)  
  11.  
  12.     static member (~-) (v:Rational) = new Rational(-v.Numer, v.Denom)  
  13.  
  14. // 使用  
  15. let r1 = new Rational(1, 2)  
  16. let r2 = new Rational(2, 3)  
  17. let r3 = r1 - r2  
  18. let r4 = -r1 

至于运算符的优先级、相关性等性质,都与上文描述的保持一致。

原文标题:总结一下F#中运算符的定义规则

链接:http://www.cnblogs.com/JeffreyZhao/archive/2009/12/14/fsharp-operator.html

责任编辑:彭凡 来源: 51CTO
相关推荐

2010-03-05 10:04:38

Python运算符

2009-11-17 09:13:21

PHP运算符

2009-08-14 10:16:57

C#运算符重载

2009-11-18 09:02:55

PHP运算符

2009-08-11 15:51:08

C#运算符算术运算符

2009-08-12 15:02:49

C#赋值运算符简单赋值运算符

2009-08-12 15:20:18

C#赋值运算符复合赋值运算符

2017-09-05 16:17:35

JavaScript运算转换

2023-04-10 08:58:13

C#关系运算符

2017-09-13 10:58:51

JavaScript转换规则

2010-01-21 17:39:26

VB.NET运算符过程

2010-01-26 08:25:06

F#语法F#教程

2016-10-14 14:04:34

JAVA语法main

2009-08-12 09:30:10

C#??运算符

2010-01-20 14:06:36

VB.NET运算符组合

2009-08-12 11:20:51

C#运算符重载

2024-02-26 15:17:20

2021-12-15 10:25:57

C++运算符重载

2009-06-21 13:48:05

ShellLinux运算符

2009-08-12 10:47:03

C#运算符重载
点赞
收藏

51CTO技术栈公众号