`
tianshibaijia
  • 浏览: 1126091 次
文章分类
社区版块
存档分类
最新评论

6.1.2 自定义运算符

 
阅读更多

6.1.2 自定义运算符

定义自定义的运算符的方式类似于函数,使用 let 绑定。它们可以使用任何字符,可以是通常的 F# 数学运算符(+/-*<>),或者是逻辑运算符(& | =),还可以是其他字符 ($%.?@^~!)。声明一个运算符,要把它的名字括在括号中,这是与通常的 let 绑定的唯一区别。使用星号时要小心,因为,(* 用于 F# 多行注释的开始。在这种情况下,解决方案是在星号与括号之间加上空格。清单 6.2 显示了如何声明和使用一个简单的运算符来处理字符串。

Listing 6.2 Working with strings using a custom operator (F# Interactive)

> let (+>) a b = a + "/n>> " + b;;
val ( +> ) : string -> string –> string

> printfn ">> %s" ("Hello world!" +>
"How are you today?" +>
"I'm fine!");;
>> Hello world!
>> How are you today?
>> I'm fine!

使用自定义的运算符而不用函数的好处,是可以使用中缀表示法(infix notation)。这意味着,对于 concat "A" (concat "B" "C"),可以写成 "A"+>"B"+>"C"。这在多次应用运算符时是特别有用的,就像我们前面的示例,因为,不必把每个调用括在括号中。

在清单 6.2 中,我们声明一胩中缀运算符,它取两个参数。F# 也允许定义一元运算符,只取一个参数,用于前缀表示法(prefix notation)。内置前缀运算符的一个示例是一元负,写成为 -1。这种运算符不基于声明中参数的数目,因为,它可能是不明确的。这有点棘手,但是,可以写一元运算符,返回一个函数,那么纯粹基于这个类型签名,它看起来像二元运算符(由于科里化,我们在第 5 章讨论的)。前缀和缀运算符之间的这种区别是基于第一个符号。当定义前缀运算符,必须以 ~ 或 !符号开始。

在 C# 中模拟自定义运算符

在 C# 中,不能声明新的运算符,虽然可以重载现有的运算符。然而,在某种程度上,使用扩展方法,可以实现相同的模式。这是 C# 3.0 中的一个新功能,下面,我们简要介绍一下。

扩展方法

在 C# 中,每个方法必须被包装在一个类中,处理对象的操作是类声明的一部分,可以使用点表示法来调用方法。扩展方法给我们一种添加新方法的方式,来处理对象,而无需修改原始的类声明。以前,这可能要通过写静态方法才行,就像这样:

StringUtils.Reverse(str);

这是很不切实际的,因为,在某个 “Utils” 类中找到一个静态方法是相当困难的。C# 3.0 中,我们可以实现 Reverse,作为扩展方法,以这种方式调用:

str.Reverse();

实现扩展方法是很容易的,因为,它是普通的静态方法,有一个特别的修饰符。唯一的区别,是它可以作为实例方法来调用,使用点表示法。它仍然是一个静态方法,所以,它既不能添加新字段,也不能访问对象的私有状态:

static class StringUtils {
public static string Reverse(this string str) { /* ... */ }
}

所有扩展方法都必须括在非嵌套的静态类中,它们必须是静态方法。修饰符 this 放在第一个参数之前,告诉编译器为它是扩展方法。

如果我们把前面示例中字符串连接实现为扩展方法,得到的语法会非常类似于原始的 F# 版本。清单 6.3 显示了使用标准的静态方法调用和扩展方法,写相同的代码。

Listing 6.3 Working with strings using extension methods (C#)

public static string AddLine(this string str, string next) {
return str + "/n>>" + next;
}
Console.WriteLine(
StringUtils.AddLine(
StringUtils.AddLine("Hello world!", "How are you today"),
"I'm fine!"));
Console.WriteLine("Hello world!"
.AddLine("How are you today")
.AddLine("I'm fine!"));

好处是纯粹的在可读性方面:我们能够 写该方法调用,与我们希望其发生的顺序相同,而不需要指定实现这个方法的类,也不需要额外的大括号。就像这样的情况 ,语法产生一个非常重要的差异。

F# 的流运算符

流水线的运算符(|>) 允许我们在函数的左侧写出第一个参数——即,在该函数名字前。这是非常有用的,如果我们想要调用几个处理函数,用序列中的某个值,我们想先写出要处理的值。下面的示例演示如何在 F# 中反转列表,然后取第一个元素:

List.hd(List.rev [1 .. 5])

这并不是非常优雅的,因为,写的操作顺序与执行顺序相反,将要处理的值在右侧,括在括号中。在 C# 中,使用扩展方法,我们会写成:

list.Reverse().Head();

在 F# 中,可以通过使用流运算符,来得到相同的结果:

[1...5] |> List.rev |> List.hd

尽管这看起来可能非常棘手,但是,运算符是非常简单的。它具有两个参数值:第二个(右侧)是一个函数,第一个(左侧)是一个值。运算符把值作为这个函数的参数值,并返回结果。

在某种意义上,流是类似于调用方法,在对象是使用点表示法,但是,它并不局限于内在的对象的方法。这是类似于扩展方法,所以,当我们写了一个通常用的流运算符的 F# 函数的 C# 替代方案时,我们将它作为扩展方法实现。

现在,我们已经完成了对泛型高阶函数和运算符的简短说明,终于可以看看如何使用它们解决日常函数编程问题。我们将讨论的第一个主题是使用高阶函数元组。

分享到:
评论

相关推荐

    C#本质论(第3版)

    6.1.2 private访问修饰符 6.1.3 protected访问修饰符 6.1.4 扩展方法 6.1.5 单一继承 6.1.6 密封类 6.2 基类的重写 6.2.1 virtual修饰符 6.2.2 new修饰符 6.2.3 sealed修饰符 6.2.4 base成员 6.2.5 构造...

    C#语言参考,微软的基础教程

    7.11.2 用户自定义的条件逻辑运算符 79 7.12 条件运算符 79 7.13 赋值运算符 79 7.13.1 简单赋值 79 7.13.2 组合赋值 79 7.13.3 事件赋值 79 7.14 表达式 79 7.15 常量表达式 79 7.16 布尔表达式 79 8. 语句 79 8.1 ...

    C#语言参考C#语言参考

    7.11.2 用户自定义的条件逻辑运算符 135 7.12 条件运算符 135 7.13 赋值运算符 136 7.13.1 简单赋值 136 7.13.2 组合赋值 138 7.13.3 事件赋值 139 7.14 表达式 139 7.15 常量表达式 139 7.16 布尔表达式 140 8. ...

    微软 C#语言参考 CHM格式

    7.11.2 用户自定义的条件逻辑运算符... 135 7.12 条件运算符... 135 7.13 赋值运算符... 136 7.13.1 简单赋值... 136 7.13.2 组合赋值... 138 7.13.3 事件赋值... 139 7.14 表达式... 139 7.15 常量表达式... 139 ...

    Objective-C2.0程序设计

    19.4 使用NSData创建自定义档案 19.5 使用归档程序复制对象 19.6 练习 第三部分 Cocoa和iPhone SDK 第20章 Cocoa简介 20.1 框架层 20.2 接触Cocoa 第21章 编写iPhone应用程序 21.1 iPhone SDK 21.2 第一个iPhone...

    Java2游戏编程.pdf

    6.1.2 MouseMotionListener接口 6.1.3 KeyListener接口 6.1.4 其他的EventListener类 6.2 总结 6.3 练习 第7章 用Java 2-D来绘制图形、文字和图像(第一部分) 7.1 坐标空间 7.2 Graphics2D类 7.3 使用仿射变换 7.4 ...

    go开发实战.doc

    6.1.2 GOPATH设置 35 6.2 包 35 6.2.1 自定义包 35 6.2.2 main包 36 6.2.3 main函数和init函数 36 6.2.4 导入包 38 6.3 测试案例 40 6.3.1 测试代码 40 6.3.2 GOPATH设置 42 6.3.3 编译运行程序 43 6.3.4 ...

    21天学通Java-由浅入深

    41 2.5.3 文档注释用户自定义类型 41 2.6 综合练习 43 2.7 小结 43 2.8 习题 43 第3章 运算符(精彩视频:43分钟) 45 3.1 算术运算符 45 3.1.1 “+”:加法运算符 45 3.1.2 “-”:减法运算符 46 3.1.3 “*”:乘法...

    C++ Primer中文版(第5版)李普曼 等著 pdf 1/3

     2.6 自定义数据结构 64  2.6.1 定义Sales_data类型 64  2.6.2 使用Sales_data类 66  2.6.3 编写自己的头文件 67  小结 69  术语表 69  第3章 字符串、向量和数组 73  3.1 命名空间的using声明 74  3.2 ...

    C++Primer(第5版 )中文版(美)李普曼等著.part2.rar

     2.6 自定义数据结构 64  2.6.1 定义Sales_data类型 64  2.6.2 使用Sales_data类 66  2.6.3 编写自己的头文件 67  小结 69  术语表 69  第3章 字符串、向量和数组 73  3.1 命名空间的using声明 74  3.2 ...

    JavaScript基础和实例代码

    6.1.2 获取目标字符串长度 6.1.3 连接两个字符串 6.1.4 验证电子邮件地址的合法性 6.1.5 返回指定位置的字符串 6.1.6 在URL中定位字符串 6.1.7 分隔字符串 6.1.8 将字符串标记为HTML语句 6.1.9 常见属性和方法汇总 ...

    源文件程序天下JAVASCRIPT实例自学手册

    6.1.2 获取目标字符串长度 6.1.3 连接两个字符串 6.1.4 验证电子邮件地址的合法性 6.1.5 返回指定位置的字符串 6.1.6 在URL中定位字符串 6.1.7 分隔字符串 6.1.8 将字符串标记为HTML语句 6.1.9 常见属性和方法汇总 ...

    南阳理工学院PHP编程讲义.rar

    5.3.2 自定义变量的初始化··80 5.3.3 变量的范围··81 5.3.4 活动变量··83 5.3.5 外界PHP 变量···· 84 5.4 运算符····85 iii 5.4.1 算术运算符··85 5.4.2 赋值运算符··86 5.4.3 位运算符·...

    visualC++2010入门经典源代码

    6.1.2 函数指针作为实参 249 6.1.3 函数指针的数组 250 6.2 初始化函数形参 250 6.3 异常 252 6.3.1 抛出异常 253 6.3.2 捕获异常 254 6.3.3 mfc中的异常处理 255 6.4 处理内存分配错误 256 6.5 函数重载 ...

Global site tag (gtag.js) - Google Analytics