作者:Alexey Bykov@EastBancTech
原文:http://bit.ly/1nGroOz
翻译:kk1982.com
转载请注明
简介
F#是由微软研究团队为.NET平台研发的一种现代函数式语言。该语言自从2005年开始研发,到2013年12月份发布了3.1版本。F#最开始只是一个学术研究项目,经过若干年的发展,已经成为了一门成熟的语言,并被众多商业公司所使用,尤其在财务领域。
之所以要研发一门与C#大为不同的语言的原因:
- 函数范式比面向对象更适合数据计算与操作;
- 能够使用那些在C#里由于设计理念不同而无法实现的编程概念;
- 避免C#的一些继承性设计问题,这些问题由于C#已经广泛应用而无法调整了。
从语言特性的角度来说,F#是C#的超集,因此C#能做的事情F#也能做,但反之则非亦然,F#有很多事情C#做不了。
在F#与其他.NET语言之间有着完整的互通性。当F#被编译成程序集后,就可以被任何一种.NET语言使用,包括C#和VB.NET等。这也意味着F#的代码可以跟系统中以C#写成的部分和平共处。
F#相对于C#的优势
Option取代NULL引用
C#有项危险的特性就是允许使用NULL值替代对象,这个特性是一大批易于产生而难于检测的Bug的来源,为了避免此类错误而在代码中到处增加的检查又降低了代码的可读性。
F#并不允许NULL值,取而代之的是使用option。与C#一样,option在表示缺失对象值上与NULL值意义相同,但是却能保证安全性。
代数数据类型(Algebraic Data Type)
C#对于代数数据类型(ADT)没有完整的支持,这种数据类型可以用简单的数据结构组成更为复杂的结构。C#所使用的类型是由类与结构构成的,对于Sum类型的支持极为有限,仅由枚举形式来体现。
相对的,F#对于ADT有着全面的支持,这使得其能够更为精准地以数据结构的角度去描述业务实体,减少误解数据含义的几率,从而提高代码质量。通常,F#可以通过不呈现的手段来避免不想要的情况,这样就没有可能去写出不合适的代码来。
变换(Transformation)与变动(Mutation)
C#鼓励更改数据以及使用状态机制,这代表着一旦对象被创建,就要在其生命周期内经历一系列的修改以改变其状态。依据对象当前状态的不同,可以允许对其执行或者不允许执行部分操作。要想安全地使用该对象,必须先对其进行状态检查,以确保处于正确的状态下,如果不这样做,很可能就会产生不可预计的结果甚至于产生崩溃的Bug。
而对于F#来说,则鼓励使用变换而非可变体。变换并不会修改对象,因此也不会更改其状态。F#会创建一个新对象并将值传递过去,以保持原对象的完整。这也意味着一个对象会一直保持着其被创建时的状态。只跟一个单一的状态打交道可以极大地简化开发过程,并减少代码量,因为当我们看到一个对象时,我们知道它一定只处于一种可能的状态下,因此不需要做额外的检查就可以安全地使用它。
表达式(Expression)与语句(Statement)
因为C#依赖于状态与变动,因此做变动的顺序就变得额外地重要,两条语句的位置互换都可能会产生Bug,因此在C#中语句的顺序是另外一件需要考虑的事情。相反,一个F#程序本质上来说就是一个由较小表达式拼成,且将输入值映射到部分输出值上的大表达式。表达式哪一部分先被计算并不重要,因为在一个无副作用的计算模型下,计算的顺序并不影响结果。因此从写语句转变到用表达式编程可以确保不会因为语句顺序错误而产生Bug。
分离数据与逻辑
C#通过在类与结构之下同时创建属性与方法的形式鼓励混合数据与逻辑,但是在进行序列化的时候,逻辑就需要被剔除,这也是为何当需要序列化的时候,类的数据要被转移到没有逻辑的纯数据对象上。
函数编程语言建议你不要将数据和逻辑混合在一起,这样当序列化为JSON或者XML时就更为直观与容易。
类型推断与简洁语法
C#的语法略啰嗦,对于同一件事情的表达,C#写的代码要比F#多得多。C#的啰嗦尤其表现在为方法的参数和返回值指定类型上。F#有一个高级类型推断系统,可以通过数值是怎样被使用的而推断出这些数值的类型,因此在大部分情况下推荐不要输入类型而让F#自行推断。录入的内容少了,生产力就提高了,代码也获得了更好的可维护性。一些人认为F#的语法更为优雅更易阅读。
从上至下从左至右的赋值
C#中文件,类以及方法声明的顺序并不重要,而对于F#来说,声明的顺序则严格取决于文件的顺序,因此你无法引用或使用尚未声明的东西。这看起来像是不必要的设计,但其实是一件好事儿,因为这可以强制程序员遵守规范。
天生的可测试性以及良好的设计
现代的面向对象设计准则,被称之为SOLID,在C#中可以轻易地违反,所以才需要了解规范并严格地遵循。但是,基本同样的准则却是函数式编程天生即有的。因此F#从一开始就以正确的方式编码,而写出不良的设计反而需要故意为之才行。
易用的并行运行
在C#中并行运行代码不是件容易的事情,因为存在竞态条件,会在两个线程试图同时修改同一个对象时产生。F#完全消灭了这个问题,因为它不允许对象在创建后进行修改,当需要修改对象时,需要对原对象应用一个变换而创建一个新对象,这意味着在F#中不需要做太多事情就可以直接运行并行代码。
更好的模块性
在F#中,逻辑的基础单位是函数,在C#中则是类。函数在F#中是“头等公民”,因此可以作为参数传递给其他函数或者作为函数的返回值。写一个函数比写一个类要省事儿,因此可以实现一个良好的模块化设计,以及以较低成本实现一个较为复杂的组合场景。用这种方式写出的代码更加易于维护并且易于重构。
专注于解决普遍问题而非特定问题
F#鼓励使用泛型类型而非具体类型。为泛型类型所写的算法可以作用于具体的类型如数字,字符串以及复杂对象。用一个泛型的实现去替代多个不同的类型可使得代码复用性更高,出错的几率更小。
类型提供程序
F#有一种特别的机制,用一种便捷统一的方式来处理异质数据源的数据,称作类型提供程序。类型提供程序抽象了不同来源的数据的实现细节,确保类型安全,公开设计良好的标准接口。同时还具备类型按需发现功能,只有在需要时才会载入新的类型。在这里有一个代码库,存放着针对不同数据源的数据提供程序,并由社群维护保持其持续更新。类型提供程序通过从不同数据源获取数据的能力,为开发带来了信息丰富的编程。
生成解析器的标准工具
F#有着两个标准工具:FsLex以及FsYacc,用于基于一个正式语法来生成解析器。这俩工具是根据Unix世界里的著名解析器生成工具Lex和Yacc来命名的。FsLex和FsYacc有着丰富的诊断能力,可以参与整个构建以简化开发过程。
F#的缺点
以下缺点仅当从函数式编程所习惯的准则去使用F#时才存在,如果你选择不使用这些准则,那么这些缺点也就不存在了。
陡峭的学习曲线
对于一个从未接触过函数式编程的人来说,用一种全新的思考方式来进行F#编程很可能是一个挑战。
更为复杂的数据结构
在使用变换而非变动的概念之下,需要使用更高级的数据结构才能更加有效率地操作数据。举个例子,在F#中你要使用二叉树而非C#中的哈希表。其他例子还有可能会大量使用拉链结构而非遍历器。
垃圾回收器负载重
使用变换而非变动的概念会导致创建比实际需要多的对象,这些对象需要及时被销毁,因此与只更改状态的对象相比,垃圾回收器的负担要大得多。开发人员需要在这之中谨慎地寻找一个平衡。
命名更有难度
F#不像C#那样允许对方法进行重载,所以在F#同一模块里的两个函数不能有一样的名字,这样就导致对函数进行唯一命名比较困难。制定一个统一的F#命名规范不是一件容易的事情。
缺乏高级工具
微软在为C#程序员打造工具上花费了大力气,很不幸,F#就没有那么多工具可供使用,这使得编码就不是那么舒服。F#连基本的重构工具都没有。
不是所有项目类型都适用
举一个不错的例子,开发带界面的应用程序对于F#来说就比较尴尬,因为操纵控件必须要更改其状态的,而这不是F#所擅长的事情。
总结
F#是一个现代的函数式编程语言,被设计作为面向对象编程的C#及VB语言的备选。F#从后者中继承了优秀的特性,并抛除那些危险的特性。这是为什么写F#代码更为安全并且易于维护。尽管部分项目类型不是F#的强项,但是对于那些进行密集数据计算的项目绝对应该是首选。F#对于业务问题的描述要比C#精准的多,使得其很适合作为服务端应用的候选。