如果你也会C,那不妨了解下F(4):了解函数及常用函数

函数式编程其实就是按照数学上的函数运算思想来实现计算机上的运算。虽然我们不需要深入了解数学函数的知识,但应该清楚函数式编程的基础是来自于数学。

例如数学函数,并没有指定返回值的类型,在数学函数中并不需要关心数值类型和返回值。F#代码为,F#代码和数学函数非常类似,其实这就是函数式编程的思想:只考虑用什么进行计算以及计算的结果(或者叫“输入和输出”),并不考虑怎样计算。

其实,你可以把任何程序看成是一系列函数,输入是你鼠标和键盘的操作,输出是程序的运行结果。你不需要关心程序是怎样运行的,这些函数会根据你的输入来输出结果,而其中的算法是以函数的形式,而不是类或者对象。

下面我们就先了解一些函数式编程中函数相关的东西。

了解函数

不可变性

在一个函数中改变了程序的状态(比如在件中写入数据或者在内存中改变了全局变量)我们称为副作用。像我们使用函数,无论输入是什么,返回值均为,但它的副作用是打印字到屏幕上了。

副作用并不一定不好,但却经常是很多bug的根源。我们分别用命令式和函数式求一组数字的平方和:

 

在中使用了Seq模块中的函数,这些函数将在稍候进行介绍。

可以看出,函数式代码简短了许多,且少了很多变量的声明。而且是顺序执行,若想以并行方式运行则需要更改所有代码,但只需要替换其中的和函数。

函数和值

在我们接触到的非函数式编程语言(包括C#)中,函数和数值总是有一些不同。但在函数式编程语言中,函数也是值。比如,函数可以作为其他函数的参数,也可以作为返回值(即高阶函数)。而这在函数式编程中是非常常见的。

需要注意的是,我们叫“”而不叫“变量”。因为在函数式编程中声明的东西默认是不可变的。(在F#中不完全如此,是因为F#包含了面向对象编程范式,可以说并非纯函数式编程语言。)

我们看下面以函数作为参数的代码(求一组数字的负值):

 

我们使用函数和列表作为的参数。

但很多时候我们不需要给函数一个名称,只需使用匿名函数或叫Lambda表达式。在F#中,Lambda表达式为:关键字和参数,加上箭头和函数体。则上面的代码可以更改为:

 

我们再看以函数作为返回值的例子,假设我们定义一个函数,输入一个值,返回一个该值求幂的函数:

 

其中即为函数使用参数返回的函数。其实这里涉及到闭包的内容,就不详细解释了,我们详细函数式编程时可能会再提及。

递归

递归大家都熟悉,只是在F#中声明时,需要添加关键字:

 

其实需要显示声明递归是因为F#的类型推断系统无法在函数声明完成之前确定其类型,而使用关键字后,就允许在确定类型前调用该函数。

部分函数:Partial Function

在函数式编程中,还有一个叫Partial Function(暂且叫部分函数吧)的,可以把接收多个参数的函数分解成接收单个参数,即柯里化(Currying)

我们知道,使用函数打印整数的语句为,我们定义一个打印整数的函数:

 

符函数

在F#中,如等运算符其实属于内建函数。而我们也可以使用这些符来自定义符函数。

我们用符来重新定义上面的阶乘函数:

 

需要注意的是,符函数一般需要括包裹,如果符函数的参数不止一个,则符函数是以中缀的方式来使用,例如我们用定义一个验证字符串是否和正则表达式匹配的函数:

 

而且,符函数也可以作为高阶函数的参数

管道符:和

我们再返回来看上面的平方和函数:

 

假如函数层次非常多,一层包裹一层,则可读性非常差。

在F#定义了如下符函数

 

我们称为“正向管道符”和“逆向管道符”。则上面的平方和函数可写作:

 

虽然用得不多,但常用来改变优先级而无需使用括:

 

我们也可以用函数合成符将多个函数组合成一个函数,合成符也分正向()和逆向()。

 

还是以上面的求平方和为例(即是一个部分函数):

 

常用模块函数

在上一篇中,我们了解了集合类型。在F#中,为这些集合类型定义了许多函数,分别在集合名称对应的模块中,例如Seq的相关函数位于模块中。而这也是我们最常用到的模块。

模块()是F#中组织代码的一种方式,类似于命令空间()。但F#中也是有命名空间的,其间的区别将在下一篇介绍。

下面简单介绍常用的函数,并会列出与.Net的中对应的函数。

如无特别说明,该函数在三个模块中均可用,但因为集合的实现方式不同,函数的复杂度也会有区别,在使用中根据实际情况选择合适的函数。

length

对应于Linq中的。即获得集合中元素的个数

 

虽然在中也有函数,但谨慎使用,因为Seq可能为无限序列。

exists 和 exists2

用于判断集合是否存在符合给定条件的元素,对应于Linq中的。而用于判断两个集合是否包含在同一位置且符合给定条件的一对元素。

 

第一行代码判断列表中是否包含等于3的元素,其中即为部分函数,注意为符函数。

第二行代码判断两个序列中,因为和在索引2的位置存在元素符合函数,所以返回。

forall 和 forall2

检查是否集合中所有元素均满足指定条件,对应Linq中的。

 

而和类似,但当且仅当所有元素都满足相同位置且符合给定条件才返回。接上一个代码片段:

 

find 和 findIndex

查找符合条件的第一个元素,对应Linq中的。需要注意的是当不存在符合条件的元素,将引发异常。

 

则返回符合条件的第一个元素的索引

map 和 mapi

对应Linq中的,将函数应用于集合中的每个元素,返回值产生一个新的集合。

 

与类似,不过在应用的函数中还需要传入一个整数作为集合的索引。

 

iter 和 iteri

将函数应用于集合中的每个元素,但函数返回值为unit。功能类似于循环。

而与一样需要在函数中传入一个索引。

 

filter 和 where

F#中和是一样的,对应于Linq中的。用于查找符合条件的元素。

 

fold

对应Linq中的,通过提供初始值,然后将函数逐个应用于每个元素,返回单一值。

 

首先,将初始值与第一个元素应用于函数,再将返回值与第二个元素应用于函数,依此类推……

Linq中的包含不需要提供初始值的重载,其实F#中也有对应的函数。类似的还有和等逆向操作,这里就不介绍了。

collect

对应Linq中的,展开集合并返回所有二级集合的元素。

 

其中id为模块中的函数,它的实现为,即直接对参数进行返回。

end

将两个集合类型合并成一个,对应于Linq中的。

 

zip 和 zip3

函数将两个集合合并到一个里,合并后每个元素是一个二元元组。

 

顾名思义,就是将三个集合合并到一个里。

合并后的长度取决于最短的集合的长度。

rev

函数反转一个列表或数组,在Seq模块中没有这个函数。

sort

函数基于compare函数(第二篇中的“比较”介绍过)对集合中的元素进行排序。

 

数学函数

Linq中包含、、和等数学函数。F#集合模块中也有对应的函数。

 

需要注意的是,函数需要集合中的元素支持精确除法(Exact division,即实现了函数的类型。不知道为什么是ByInt。),而F#中又不支持隐式类型转换,所以对集合求平均值只能先转换为或,或使用函数。

函数的示例代码将第一篇中由C#翻译过来的命令示示例代码转换成了函数式的代码。

集合间转换

三种集合类型的对应模块中,均提供转换到(to)另外两种集合类型,和从(of)另外两种类型转换的函数。

如Seq模块,通过和函数转出;通过和转入。

 

函数式编程,核心就是函数的运用。上面介绍的这些在C#中也经常使用到对应的方法,但F#提供的函数非常丰富,大家可通过MSDN了解更多:

因为F#中的List和Array均实现了接口,所以Seq模块的函数也可以接收List类型和Array类型的参数。当然,反之则不行。

到现在为止,我们了解的F#都是在交互窗口中。下一篇我们再简单介绍项目创建和代码组织,即模块相关。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值