总结一下F#中运算符的定义规则
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#中运算符的定义规则相关推荐
- c语言程序中unit怎么定义,c ++中的一个定义规则(One definition rule in c++)
c ++中的一个定义规则(One definition rule in c++) 根据c ++标准: 任何翻译单元都不得包含任何变量,函数,类类型,枚举类型或模板的多个定义. //--translat ...
- C++中运算符重载需要遵循的规则
一.C++中运算符重载需要遵循的规则 1.并不是所有的运算符都可以重载.能够重载的运算符包括: + - * / % ^ & | ~ ! = < > += -= *= /= %= ^ ...
- Web前端第三季(JavaScript):三:第1章:JavaScript基本知识:107-js中变量的声明+108-js中变量的注意事项+109-js中变量的命名规则+110-赋值和算术运算符
目录 一.目的 1.想:学习前端知识 2.想:记录笔记,下次不用看视频,直接看笔记就可以快速回忆. 二.参考 1.我自己代码的GitHub网址 2.SIKI学院:我参考此视频实操 3.w3school ...
- C/C++编程:单一定义规则ODR(不理解)
ODR规则 任何变量.函数.类类型.枚举类型.概念 (C++20 起)或模板,在每个翻译单元中都只允许有一个定义(其中部分可以有多个声明,但是只允许有一个定义) 在整个程序中,被ODR式使用非inli ...
- Java基础教程,第三讲,运算符 变量定义 数据类型转换
2019独角兽企业重金招聘Python工程师标准>>> 学完此次课程,我能做什么? 学完此次课程我们可以学会Java的运算符,以及数据类型的自动转换和强制转换. 学习此次课程,需要多 ...
- F#中的异步和并行设计模式(三):代理
在这个系列的第三部分,我们解释了F#中的轻量级代理的和交互式代理,并且看过了一些与之相关的典型的设计模式,包括内部隔离状态. 第一部分分描述了F#是一种并行交互式语言及如何支持轻量级交互操作的,并且为 ...
- iptables 定义规则
iptables定义规则的方式大概是这种格式: iptables [-t table] COMMAND chain CRETIRIA -j ACTION -t 表名:指定要操作的表 COMMAND:定 ...
- 介绍sendmail中mail relay的规则
介绍sendmail中mail relay的规则 以前总结和写的一些教程的一些资料,一直没时间发布到博客上面,五一到了,终于有点时间发布啦!关于Linux上面还会有RHCE系列的学习笔记发表 一.什么 ...
- 6.Java中的变量(定义)和数据类型(划分)
1.变量概述 变量:在程序的执行过程中,其值改变的量! 2.必须有一个限定,规定数据类型 (1) 基本数据类型 , 分为4类八种 ...
最新文章
- Python 越被黑越红?2 万程序员这么说......
- Mac IntelliJ IDEA 快捷键终极大全,速度收藏!
- linear model课程笔记
- python用缩进来写模块_python学习笔记
- 2021HDU多校6 - 7029 Median(思维)
- 微信小程序中this指向作用域问题this.setData is not a function报错
- geoserver安装(war安装+exe安装)
- 奥运转播加速上云,北京冬奥组委测试阿里云视频传输技术
- 开发指南专题九:JEECG微云快速开发平台-表单校验组件ValidForm
- 配置Tomcat时server.xml和content.xml自动还原问题
- 14. Window clearInterval() 方法
- Openlayer:学习笔记之解析地图组成
- vue 保留小数点厚一位_蓝盈莹真是一位有韵味的女人,羊羔绒还要拼上牛仔穿,真惹眼...
- 数据结构题集c语言版题目与答案,数据结构题集(C语言版)答案 - 严蔚敏编著...
- 关于vs2008改变工程路径
- 李雅普诺夫稳定性理论 matlab,李雅普诺夫稳定理论的定义应用解析.ppt
- 155款安卓开源项目源码整理,总有你要找的(精心收集)
- DevComponents.DotNetBar2 美化包使用以及验证教程
- 海思AI芯片3559A方案学习(一)
- java 生成条形码_JAVA 生成扫描条形码