首页 > 代码库 > F# 的血继限界(1)
F# 的血继限界(1)
如我们所见,在 F# 的产生和发展过程中,给 C# 引进了很多函数式的革新理念。然而这些革新仍未足以让 C# 进行完全函数式编程。C# 进行函数式编程时存在如下问题:
1、C# 中不能声明“局部不可变量”,不能处理尾递归优化。
1、C# 中不能声明“局部不可变量”,不能处理尾递归优化。
很遗憾,不可变量和尾递归,可以说是函数式语言的绝对标识,至于有什么好处就不赘言。 而且这两点在C++都支持,把它们放在一起说也就是因为这个原因,它们可以算是 C# 相对 C++ ,进化不完全的“罪证”。 C++中的 const 关键字,在C#里分成 const 和 readonly 两个,前者代表常量,而后者代表不可变,可惜 readonly 只能用来标识字段。替换方案:
a、使用单元素 Tuple。.net 中支持只有一个元素的 Tuple,这下可派上用场了。但是不推荐,因为 Tuple 在.net 里面是 Object,每个变量开一个 Tuple 开销大了点,如果使用多元素 Tuple,却无法使用自定义的变量名,反而增加阅读代码的难度。另外,Tuple 只能保证 Tuple 里面的成员值不变,Tuple 还是可以整个被替换掉。这点倒可以忽略,这种替换稍嫌麻烦,我们只要达到变量不会被轻易误修改的目的就行。
a、使用单元素 Tuple。.net 中支持只有一个元素的 Tuple,这下可派上用场了。但是不推荐,因为 Tuple 在.net 里面是 Object,每个变量开一个 Tuple 开销大了点,如果使用多元素 Tuple,却无法使用自定义的变量名,反而增加阅读代码的难度。另外,Tuple 只能保证 Tuple 里面的成员值不变,Tuple 还是可以整个被替换掉。这点倒可以忽略,这种替换稍嫌麻烦,我们只要达到变量不会被轻易误修改的目的就行。
b、 使用匿名类型,例如:var locals = new { Value = http://www.mamicode.com/10 };
这种方法比用 Tuple 好一点。如果多个变量一起放在大括号里,可以保证只读性,也没有太大开销。
只是代码需要相当多的调整。会不会因此反而丢失可读性,看施主的因缘际遇了。
c、最实用的方法,也是没有办法的办法:放弃不可变的追求,把函数缩短,尽量写短的函数,增强可读性,方便重构,好处多多。
至于尾递归的替换方案,无非就是少用递归,多用循环,也没什么好的建议了。
2、C# 中缺乏元组、记录( record )。
元组其实很像 C 里面的 struct,只是它不保证内存是连续的。F# 语法上表示的元组,实际上传递的是松散值,只有在对外接口时,它被才被编译成 Tuple 对象。所以元组在 F# 中被广泛应用,而不影响程序性能。C# 中不能表达元组的概念,如果不想每用一个元组就声明一个struct,也可以用 Tuple 对象来表示,只是有一定额外开销,对于通常只有两三个值的元组来说,Tuple 太不轻便了。而且它的元素只能用 Item1,Item2 ... 来表示,很不直观。Tuple 刚推出来的时候,不少文章说可以用 Tuple 来替代 out, 但是这种意见很快就被反对的声音淹没了,毕竟不是同一个重量级的东西啊。
这种方法比用 Tuple 好一点。如果多个变量一起放在大括号里,可以保证只读性,也没有太大开销。
只是代码需要相当多的调整。会不会因此反而丢失可读性,看施主的因缘际遇了。
c、最实用的方法,也是没有办法的办法:放弃不可变的追求,把函数缩短,尽量写短的函数,增强可读性,方便重构,好处多多。
至于尾递归的替换方案,无非就是少用递归,多用循环,也没什么好的建议了。
2、C# 中缺乏元组、记录( record )。
元组其实很像 C 里面的 struct,只是它不保证内存是连续的。F# 语法上表示的元组,实际上传递的是松散值,只有在对外接口时,它被才被编译成 Tuple 对象。所以元组在 F# 中被广泛应用,而不影响程序性能。C# 中不能表达元组的概念,如果不想每用一个元组就声明一个struct,也可以用 Tuple 对象来表示,只是有一定额外开销,对于通常只有两三个值的元组来说,Tuple 太不轻便了。而且它的元素只能用 Item1,Item2 ... 来表示,很不直观。Tuple 刚推出来的时候,不少文章说可以用 Tuple 来替代 out, 但是这种意见很快就被反对的声音淹没了,毕竟不是同一个重量级的东西啊。
record 是个轻量级的类,某 MVP 说,它就是个带标签的元组。但是我们看到,record 是对象 ,而不是值类型。
record 某种程度上也很像 C 里面的 struct,相对元组来说,它继承的是 C struct 的另一些特性:
第一,它的成员带名字标签。C struct 是 C++ class之祖,希望不要介意我这样说。
第二,它在 F# 内部可以当接口使用。因为 record 的属性可以是函数类型,C 的 struct 也可以包含函数指针。所以它们都常常用来做简单的接口。
而 record 第三个跟 C stuct 相像的特性,在函数式编程里面尤其重要: 它没有构造函数,不可继承,它的属性一旦声明就会公开。它是如此简单的一个类,只要检查 record, 看看它的属性是否可变,就可以知道它是不是一个不可变的类。我们可以把不可变的 record 当成一个值来优化(类似不可变的 string )。
3、 C# 声明序列的方式很单调。
函数式编程的一个重要特征,就是延迟式计算,而序列是延迟计算的一个常用方式,
譬如,我要对一个大范围的 IP 地址发查询码,直至有一个 IP 有回应为止,我无须一开始就生成大波 IP 地址,只须写一个 IP 地址枚举器(也就是序列),每发一个码,生成一个新的 IP 即可,(仅作例子,不谈实际操作)。
在 F# 中,到处都可以使用 seq { ... } 的方式声明一个序列(F# 的大括号系列有很多方便的应用,以后会一一提到)。
C# 只有两种方式产生序列的方式:
a、可以通过 Linq.Enumerable,通常你必须先有另一个序列,不是通用方式。
b、写一个返回 IEnumerable 接口的函数,是通用方式,但是不方便。另外,匿名函数里面不支持 yield return。增加了使用 IEnumerable 的死板系数。基本上,你要用 IEnumerable 接口,你就只能在你当前的函数外面另外写一个函数,因此,你就不能做闭包采集,有时不得不写出一大串调用参数。
F# 的血继限界(1)
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。