设为首页 加入收藏

TOP

F# 4.1全面概览(一)
2018-01-17 13:05:33 】 浏览:529
Tags:4.1 全面 概览

语义化版本(Semantic Versioning)有时颇具误导性。虽然F# 4.1向后兼容4.0版,但是它完全不是一个小的版本。F# 4.1预览版自发布以来,得到了来自Microsoft以及更大程度上来自于社区的贡献,因此F# 4.1在性能、互操作性和便利性等方面上新增了一些特性。


F# 4.1发布的重头是使用结构体(structs)的能力。结构体也称为值类型(value type),它并非引用类型(reference type)。结构体能从堆栈上分配值并将嵌入到其它对象中,使用正确时可对性能产生巨大影响。


结构体中首先要介绍的是结构体元组。对于F#和其它函数式编程语言而言,在惯用代码中元组是非常重要的。一个对F#实现的主要批评是“System.Tuple”元组是引用类型的,这意味着每次创建一个元组时,可能需要进行代价昂贵的内存分配。作为不可变对象,这是时常发生的。


通过在.NET中引入ValueTuple类型,这一问题得到了解决。VB和C#也使用这一值类型,当内存具有压力和垃圾回收周期成为问题时,它会改进性能。但在使用中应该慎重,因为重复拷贝16个字节以上的ValueTuples可能会带来其它的性能损失。


在F#中,使用struct标注可以将一个元组定义结构体元组,而非标准元组。该定义所生成的类型与标准元组的工作机制类似,但是两者并不兼容,两者间的转换是一种破坏性更改。例如: 


如果出于性能的原因而采用了结构体元组,进行测试是十分重要的。由于元组在F#中广为使用,因此编译器对元组有特殊的优化机制,有时会完全地清除元组。这样的优化机制可能不必用于结构体元组。正如Arbil在原始提案中所写的:“据我们的测试,如果考虑上垃圾回收的代价,短结构体元组的性能可达标准元组的25倍。”


该特性可扩展为一种称为“结构推演”的特性。想想下面的代码:


在F#中,该代码可能会产生编译器错误。这是因为origin是一个结构体元组,表达式(x0,y0)表示一个引用元组。如果能实现结构推演,那么此代码中会隐含地使用struct关键字。


鉴于这是一个编译器错误,为避免对编译器做破坏性更改,该特性可能会在今后的版本中实现。由于它会对语义和编译器产生大量影响,因此并不保证该特性将一定会出现。


另一个F#编程中的重要概念是使用记录类型。记录类型在很多方面上类似于元组,例如都是不可变的,都具有固定的大小。但是两者间的最大差别在于,记录中的每个域都具有不同的名字,而元组则依赖于实际位置区分各个域。


一般说来,软件库开发人员更愿意在公开API中使用记录,而非元组,因为命名的域更易于应用开发人员的理解。


不幸的是,记录面对着和元组同样的问题,即它们通常都是值类型,或者曾经作为值类型使用。F#的贡献者Will Smith(网名TIHan)在创建了结构体记录时,部分参考了结构体元组的工作。


要将一个类型标识为结构体记录,而不是一般情况下的引用类型记录,必须使用[<Struct>]属性。你可能会疑惑为什么不能使用struct关键字。对此网友Dsyme是这样解释的:


警告:F# 4.0并不兼容结构体记录。这是编译器的一个瑕疵,该瑕疵导致编译器将结构体记录看成是一种引用类型,而非值类型。如果你的库有可能被使用旧版本编译器的人调用,就不要使用这个特性。


继续F#结构体这一话题,现在我们看一下结构体差别联合(Struct Discriminated Unions)。差别联合在本质上等价于C++等语言中的联合类型,只是额外具有一些句法上的小技巧。例如,可以使用类似于“case标识符”的形式在差别联合中有效地定义新类型,例如:


在上面的例子中,Shape联合具有三个子类型,即Rectangle、Circle和Prism,它们只存在于Shaple的上下文中。一个指定的Shape实例中,只能包含三个子类型中的一类。


可能你并不熟悉F#的语法,在类型定义中,各个域是通过星号“*”分隔的。因此子类型Rectangle具有两个域,Circle具有一个域,而Prism具有三个域(其中有一个域未命名)。


如果某个“case标识符”具有一个以上的域,就实现为一个元组。这会使我们回想起这一特性的初衷所在。差别联合允许实现为值类型,而不是引用类型。


警告:正如对结构体记录一样,F# 4.0编译器将不能正确地解释结构体差异联合。


C# 7中添加了一个称为“ref locals”的新特性,允许指向值的安全指针。值可以是一个对象内部由ref关键字所指定的参数,在一些情况下也可以指向堆栈上的值。想想如下的简单例子:


实现同样功能的F#代码类似于:


在该特性的公告和RFC中,均称F#已通过“引用单元”(Reference Cells)支持ref locals。虽然这种说法并不正确,但是也可以理解,因为该特性的语法的确类似于C#的ref locals。例如:


但是在查看引用单元的源代码后,事情就变得十分清楚了,该特性实际上只是包装了一个可变值。相关的源代码如下:


因此在上面的例子中,命名为y的变量并未真正地引用了数组a中的元素。y仅是在FSharpRef<int>对象中存储的一个拷贝。如果不是因为“ref locals”的语法与“引用单元”差别不大,则会引发混淆。


F# 4.1突出强调的另一个方面,就是确保F#代码能与其它语言所编写的库进行良好交互。因为.NET已深入挂接到C、COM及一些动态编程语言中,这意味着仅使用C#软件库是不够的。


该特性只对那些需要从F#调用C库的开发人员有用。如果要将一个数据结构传递给C库,并且该C库需要保持该结构,这时你会碰到一些严重的问题。不同于.NET语言,C并不希望背后有垃圾回收器移动内存中的对象。


解决方案是将对象“钉”在内存中,以防止垃圾回收器移动对象。开发人员必须谨慎,不要滥用这一特性,因为它会对内存使用产生消极影响。


在F#中,该功能是使用use关键字fixed关键字联合实现的。这可能会对一些编程人员造成困惑,因为use关键字非常类似于C#的using关键字,通常用于IDisposable对象上。在这种情况下,use关键字仅提供关联变量的范围,并确保了在该范围之外会解除内存的钉住状态。


在.NET中,Caller信息是使用由CallerFilePath、CallerLineNumber或CallerMemberName属性装饰的可选参数实现的,主要用于日志,也可在其他的场景中看到,例如支持WPF/XAML应用中的属性更改通知


在F#中,无需特别介绍该特性。根据RFC,F#需要该特性以符合.NET标准,因此必须要实现该特性。


如果简单地将.NET风格的可选参数放入F#中,它并不会正确的工作。理论上,你可以将[<Optional;DefaultParameterValue<(...)>]置于参数上,并获得与VB和C#中同样的可选参数行为。但是F# 4.0及更早的版本并不能正确地编译DefaultParameterValue属性。这意味着该属性在所有语言中被忽略了。


与此相关的问题是,虽然F#可以使用其它库编译后的可选参数和默认参数,但是它不能在同一组装中的代码中使用它们。这一问题只会影响到.NET风格的可选参数,F#风格的可选参数仍按预期工作。


在“RFC FS-1027 Optiona

首页 上一页 1 2 3 下一页 尾页 1/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇Java 获取ip地址和网络接口 下一篇Java URL和URLConnection

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目