函数式编程-Kotlin

什么是函数式编程?网上答案五花八门,有从历史开始阐述,有从Lambda演算开始,有从函数式编程和面向过程和面向对象的比较出发,有从函数式编程的特点闭包,高阶函数,柯里化(currying)开始,有从特定语言出发(JS,python,kotlin)开始讲。有的很吓人,有的很啰嗦,有的过于简单,有些不适合入门。我提取了前人的一些精华和自己的一些感悟斗胆尝试解释一下,希望对入门的同学有所帮助,就欣慰不已。

函数式编程中,函数是“一等公民”,“一等公民”在所有介绍函数式编程的文章都有提到,“一等公民”指的是函数和变量对象地位是一样的,可以将函数赋值给变量,可以作为参数,可以作为函数的返回值,函数式编程的其他特点都是由这三个特点推导出来的,这三个特点是函数式编程的基础。可能有人会举手,这三个特点在面向过程和面向对象语言中,函数指针可以实现类似的功能。的确,函数指针确实可以实现类似功能,也许那些支持函数式编程的语言在底层可能就是通过函数指针来实现的。只不过是支持函数式编程语言由于对这部分有了专业的支持,在实现上更简单,更方便。这三个特点对于熟悉面向过程和面向对象编程的同学仍然会有一些别扭。下面我就通过Kotlin语言来介绍一下这三个特点。大家可以通过Kotlin官网 这个网址的“TRY ONLINE”运行相关代码,如果对Kotlin不是很熟悉,只要看一下如何定义变量,如何定义函数就可以了,最好能够实际操作一下,想必是极好的。

1 将函数赋值给变量

这条就是讲可以将一个函数赋值给一个变量,听起来好像是废话,但是您在此停留几分钟,细细思考一下,由此能够推导出的结论丰富有趣,强烈建议大家真的抽出几分钟仔细思考一下。由于可以将函数赋值给变量,那么函数作为函数返回值,作为参数那就理所应当。多个变量可以同时引用一个函数,可以不使用函数名调用函数了,函数名就可以忽略,完全靠变量来调用函数,那么就可以定义没有函数名的函数,也就是匿名函数,说得高大上就是Lambda表达式。当然还有很多推论,希望大家留言。

下面这个例子无需多讲,“::” 可以取出函数的地址,这个例子和函数指针非常类似。后面讲到函数可以作为返回值,还会详细讲返回值赋值给变量,那部分更像函数赋值给变量。函数式编程-Kotlin
2 函数作为参数

这条就是说函数可以作为参数进行传递,这条也是相当重要。如果函数可以作为参数进行传递,那么就可以将不同函数进行组合,提高代码的复用,代码会更简洁,这部分就可以引出高阶函数,类似f(g(x))的形式,这些都是必然的。

下面的例子isOdd和isOdd2是等价的,都是判断一个整数是否是奇数,只是isOdd2写法更简单。而filter可以接受isOdd和isOdd2作为参数,将numbers中是奇数的全部过滤出来并返回。
函数式编程-Kotlin
下面的例子是自行定义了一个函数,有三个参数,一个是整数,一个是String,一个是函数(参数是一个整数,返回类型是Boolean)。
函数式编程-Kotlin
下面是调用的结果,根据传入的整数,string和函数打印出不同的结果,isOdd和isEven和整数,String一样可以作为参数,函数体内调用传入的函数参数。可以自行想象一下如果是面向过程和面向对象如何实现这个功能,比较一下。
函数式编程-Kotlin
3 函数作为返回值

函数作为返回值是函数式编程非常重要的部分,如果函数可以作为返回值,那么函数内应该可以定义函数,并且函数可以返回函数内定义的函数。如果一个函数只能返回外部函数,函数内不可以定义函数,那就显得低级了,很多功能也就不能实现了,例如闭包(后续文章会讲到)。函数内定义函数,也是函数作为“一等公民”的象征,既然都是“一等公民”,为什么不可以在函数内定义函数,变量和对象都可以呀。从这个角度说,函数内定义函数也是必须的。定义内函数作用和功能都很明显,函数可以封装得更好,可以返回内部函数,可以访问函数内的局部变量,因为内函数可以访问外函数的变量,而内函数又可以被作为返回值,这部分就是重要的闭包功能,和变量的作用域相关,后续文章会讲到。

下面这个例子是返回外部函数,通过传入的整数参数,决定应该返回isOdd还是isEven,并将结果返回给一个变量,这个变量就作为函数的替身使用了。
函数式编程-Kotlin
下面在一个函数内部返回了一个代码块,而代码块没有名字,其实代码块一个匿名函数,没有参数,也没有返回值,类似void,返回的代码块对外部函数定义的变量count进行了自增操作,并且打印出来。
函数式编程-Kotlin
下面是调用的部分和返回结果,调用了三次,结果是1,2,3,是否和您想象的结果一样,能否给出合理的解释原因呢(文章里多次提到的闭包)。这是非常有用的,如果想记录一个函数被调用了几次,那么在面向过程编程中一般需要定义全局变量来记录,但是函数式编程用局部变量就能做到,而且更合理,因为这个变量只和这个函数有关,定义成函数的成员最合理。(在此简单解释一下原因,printCount返回了一个内部匿名函数,而匿名函数使用了printCoun的变量count,并且printCount返回时将内部的匿名函数返回给了main()中的一个变量m_printCount,那么m_printCount就通过匿名函数引用到了printCount函数的变量count。即使匿名函数执行完了,而count是不会被释放的,因为m_printCount还没有释放,count需要等到m_printCount释放后,才能释放,换句话说,count在这种情况下被cache了,count好像是变成了全局变量。说得这么啰唆,您应该明白我的意思了吧,如果还是有疑惑,也没有关系,后续还会讲到。通过这种方式能够访问函数的局部变量。)
函数式编程-Kotlin
下面这个例子decreaseCounter有一个整数参数,返回一个函数,该函数没有参数,返回类型是整数,连续调用了三次,大家可以根据上面的例子,分析一下结果。
函数式编程-Kotlin
下面是一个自增的例子,分析一下结果。请注意返回函数的不同的写法。
函数式编程-Kotlin
最后一个例子,如何调用这个add,功能是什么。
函数式编程-Kotlin
请看一下调用的结果,是否和您想的一样。如果不一样,请再仔细想一想。
函数式编程-Kotlin
最后一个例子其实就是下面add2函数的柯里化(currying),多参数转变为单参数,可以达到延迟计算的效果。
函数式编程-Kotlin
在下一篇文章中将讲述闭包,组合函数(高阶函数)和柯里化。