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

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

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

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

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

运算符重载

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

// operator.fs
#lightmodule Operatorlet (+) (a:int) (b:int) = a * b

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

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

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

运算符的组成

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

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

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

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

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

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

前缀与中缀运算符

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

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

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

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

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

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

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

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

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

运算符的优先级

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

3 + 4 * 5 |> printfn "%i" // 23

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

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

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

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

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

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

运算符的相关性

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

例如,最普通的“除号”便是左相关的:

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

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

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

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

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

let (>+) (x:int) (y:int) = x + y
let (>*) (x:int) (y:int) = x * y3 >+ 4 >* 5 |> printfn "%i" // 35
3 >* 4 >+ 5 |> printfn "%i" // 17

而“^”是右相关的:

let (^+) (x:int) (y:int) = x + y
let (^*) (x:int) (y:int) = x * y3 ^+ 4 ^* 5 |> printfn "%i" // 23
3 ^* 4 ^+ 5 |> printfn "%i" // 27

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

一元运算符

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

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

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

为类型定义运算符

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

// 定义
type Rational(numer, denom) =member r.Numer = numermember r.Denom = denomstatic member (-) (x:Rational, y:Rational) =let n = x.Numer * y.Denom - y.Numer * x.Denomlet d = x.Denom * y.Denomnew Rational(n, d)static member (~-) (v:Rational) = new Rational(-v.Numer, v.Denom)// 使用
let r1 = new Rational(1, 2)
let r2 = new Rational(2, 3)
let r3 = r1 - r2
let r4 = -r1

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

转载于:https://www.cnblogs.com/JeffreyZhao/archive/2009/12/14/fsharp-operator.html

总结一下F#中运算符的定义规则相关推荐

  1. c语言程序中unit怎么定义,c ++中的一个定义规则(One definition rule in c++)

    c ++中的一个定义规则(One definition rule in c++) 根据c ++标准: 任何翻译单元都不得包含任何变量,函数,类类型,枚举类型或模板的多个定义. //--translat ...

  2. C++中运算符重载需要遵循的规则

    一.C++中运算符重载需要遵循的规则 1.并不是所有的运算符都可以重载.能够重载的运算符包括: + - * / % ^ & | ~ ! = < > += -= *= /= %= ^ ...

  3. Web前端第三季(JavaScript):三:第1章:JavaScript基本知识:107-js中变量的声明+108-js中变量的注意事项+109-js中变量的命名规则+110-赋值和算术运算符

    目录 一.目的 1.想:学习前端知识 2.想:记录笔记,下次不用看视频,直接看笔记就可以快速回忆. 二.参考 1.我自己代码的GitHub网址 2.SIKI学院:我参考此视频实操 3.w3school ...

  4. C/C++编程:单一定义规则ODR(不理解)

    ODR规则 任何变量.函数.类类型.枚举类型.概念 (C++20 起)或模板,在每个翻译单元中都只允许有一个定义(其中部分可以有多个声明,但是只允许有一个定义) 在整个程序中,被ODR式使用非inli ...

  5. Java基础教程,第三讲,运算符 变量定义 数据类型转换

    2019独角兽企业重金招聘Python工程师标准>>> 学完此次课程,我能做什么? 学完此次课程我们可以学会Java的运算符,以及数据类型的自动转换和强制转换. 学习此次课程,需要多 ...

  6. F#中的异步和并行设计模式(三):代理

    在这个系列的第三部分,我们解释了F#中的轻量级代理的和交互式代理,并且看过了一些与之相关的典型的设计模式,包括内部隔离状态. 第一部分分描述了F#是一种并行交互式语言及如何支持轻量级交互操作的,并且为 ...

  7. iptables 定义规则

    iptables定义规则的方式大概是这种格式: iptables [-t table] COMMAND chain CRETIRIA -j ACTION -t 表名:指定要操作的表 COMMAND:定 ...

  8. 介绍sendmail中mail relay的规则

    介绍sendmail中mail relay的规则 以前总结和写的一些教程的一些资料,一直没时间发布到博客上面,五一到了,终于有点时间发布啦!关于Linux上面还会有RHCE系列的学习笔记发表 一.什么 ...

  9. 6.Java中的变量(定义)和数据类型(划分)

    1.变量概述 变量:在程序的执行过程中,其值改变的量! 2.必须有一个限定,规定数据类型 (1) 基本数据类型   ,  分为4类八种                                 ...

最新文章

  1. Python 越被黑越红?2 万程序员这么说......
  2. Mac IntelliJ IDEA 快捷键终极大全,速度收藏!
  3. linear model课程笔记
  4. python用缩进来写模块_python学习笔记
  5. 2021HDU多校6 - 7029 Median(思维)
  6. 微信小程序中this指向作用域问题this.setData is not a function报错
  7. geoserver安装(war安装+exe安装)
  8. 奥运转播加速上云,北京冬奥组委测试阿里云视频传输技术
  9. 开发指南专题九:JEECG微云快速开发平台-表单校验组件ValidForm
  10. 配置Tomcat时server.xml和content.xml自动还原问题
  11. 14. Window clearInterval() 方法
  12. Openlayer:学习笔记之解析地图组成
  13. vue 保留小数点厚一位_蓝盈莹真是一位有韵味的女人,羊羔绒还要拼上牛仔穿,真惹眼...
  14. 数据结构题集c语言版题目与答案,数据结构题集(C语言版)答案 - 严蔚敏编著...
  15. 关于vs2008改变工程路径
  16. 李雅普诺夫稳定性理论 matlab,李雅普诺夫稳定理论的定义应用解析.ppt
  17. 155款安卓开源项目源码整理,总有你要找的(精心收集)
  18. DevComponents.DotNetBar2 美化包使用以及验证教程
  19. 海思AI芯片3559A方案学习(一)
  20. java 生成条形码_JAVA 生成扫描条形码

热门文章

  1. Share memory中bank conflict问题
  2. 【mysql解决办法】insert into select 想插入的数据如果部分为空怎么办?
  3. 整数划分问题【递归以及递推求解方式】
  4. UDP实现全双工聊天(聊天工具进阶)pyhton
  5. 10.4 实现关系下的匿名内部类
  6. 采集文件到kafka
  7. 不定宽高的div水平、垂直居中问题
  8. swoole使用 常用案例
  9. 随机分配座位,共50个学生,使学号相邻的同学座位不能相邻
  10. 泛型类型通常在Dao和Service 中使用BaseDaoT extends Serializable的泛型