Kotlin函数与函数式编程浅析

Kotlin函数与函数式编程浅析

如果你对Kotlin语法一无所知,推荐先阅读官方文档或者中文站(https://www.kotlincn.net/docs/reference/)之后再看这篇文章会有更深刻的理解。本篇文章主要介绍Kotlin函数的用法,以及自己对函数式编程的一些理解。并且会和Python,C++做一些比较。

下面是维基百科上对于函数式编程的定义:

函数式编程(英语:functional programming)或称函数程序设计,又称泛函编程,是一种编程范型,它将电脑运算视为数学上的函数计算,并且避免使用程序状态以及易变对象。函数编程语言最重要的基础是λ演算(lambda calculus)。而且λ演算的函数可以接受函数当作输入(引数)和输出(传出值)。

下面是关于高阶函数的定义:

在数学和计算机科学中,高阶函数是至少满足下列一个条件的函数:接受一个或多个函数作为输入,输出一个函数

不难推断出函数式编程最重要的基础是高阶函数。也就是支持函数可以接受函数当作输入(引数)和输出(传出值)。

函数作为Kotlin中的一级公民可以像其他对象一样作为函数的输入与输出,这也就是Java程序员转到Kotlin觉得变化最大,最难理解的一点。如果你之前学过Python或者C++11可能会对此比较容易接受。这也是为什么本文以介绍Kotlin的函数及函数式编程为主。

Kotlin 函数

下面是Kotlin中一般的函数定义,和Java不同的是函数形参,返回值类型置后。函数体可以用等号赋值给函数定义,这里也可以看出函数和变量的平等性。

fun main(args: Array) { 


    var s = sum(1,2) 


    var m = multi(2,3) 


    var x = maxOf(3,4) 


} 


 


fun sum(a: Int, b: Int): Int { 


    return a + b 


} 


 


fun multi(a: Int, b: Int): Int = a * b 


 



fun maxOf(a: Int, b: Int): Int = if (a > b) a else b  

另外Kotlin还支持函数默认参数,拓展函数,中缀表达式,下面是简单的例子:

fun main(args: Array) { 


    isBiggerThan(2) 


    isBiggerThan(2, 5) 


    var s = "a".isLetter() 


    var a = 1 add 2 


} 


 


fun isBiggerThan(a: Int, b: Int = 0) { 


    return a > b 


} 


 


//拓展函数 


fun String.isLetter(): Boolean { 


    return matches(Regex("^[a-z|A-Z]$")) 


} 


 


//拓展函数,中缀表达式 


infix fun Int.add(x: Int): Int { 


    return this + x 



}  

支持默认参数的函数可以减小函数的重载。

String对象中本没有判断是否是字母的方法,在Java中我们一般会定义一些Utils方法,而在Kotlin中可以定义类的拓展函数。

第二个例子是给Int类定义了一个拓展函数,并且该拓展函数以中缀表达式表示,给予了开发者定义类似关键字的权利。

比如我们可以这样创建一个map对象:

val kv = mapOf("a" to 1, "b" to 2) 

这里的to就是一个中缀表达式,定义如下:

public infix fun<A, B> A.to(that: B): Pair<A, B> = Pair(this, that) 

Pair就是Map中存的对象,所以你也可以这样创建

val kv = mapOf(Pair("a", 1), Pair("b", 2)) 

在Python中如果我们想让函数返回多个值,可以返回一个元组,Kotlin基于解构原则也可以实现类似的功能:

fun main(args: Array) { 


    val (index, count) = findWhere("abcabcabcabc", 'c') 


} 


 


fun findWhere(str: String, findChar: Char): Pair<Int, Int> { 


    var index = -1 


    var count = 0 


    for ((i, v) in str.withIndex()) { 


        if (v == findChar) { 


            if (index == -1) { 


                index = i 


            } 


            ++count 


        } 


    } 


    return Pair(index, count) 



}  

自定义对象如何支持解构请查看官方文档,map支持解构,所以可以像下面这样遍历:

for ((k, v) in map) { 


    print("$k -> $v, ") 



}  

高阶函数与 Lambda 表达式

“Lambda 表达式”(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数。Lambda表达式可以表示闭包(注意和数学传统意义上的不同)。

Python中的lambda表达式:

add = lambda x, y:x+y 

C++中的lambda:

[](int x, int y) -> int{ return x + y; } 

Kotlin中的lambda:

var add = {x: Int, y: Int -> x + y} 

Kotlin 作为一个强类型语言还是比较简洁的。

我们可以这样使用一个lambda表达式:

fun main(args: Array) { 


val sumLambda = {a: Int, b: Int -> a + b} 


sumLambda(1, 2) 



}  

它可以像函数一样使用()调用,在kotlin中操作符是可以重载的,()操作符对应的就是类的重载函数invoke()。

你还可以想下面这样定义一个变量:

val numFun: (a: Int, b: Int) -> Int 

它不是一个普通的变量,它必须指向一个函数,并且函数签名必须一致:

fun main(args: Array) { 


    val sumLambda = {a: Int, b: Int -> a + b} 


    var numFun: (a: Int, b: Int) -> Int 


    numFun = {a: Int, b: Int -> a + b} 


    numFun = sumLambda 


    numFun = ::sum 


    numFun(1,2) 


} 


 


fun sum(a: Int, b: Int): Int { 


    return a + b 



}  

可以看到这个变量可以等于一个lambda表达式,也可以等于另一个lambda表达式变量,还可以等于一个普通函数,但是在函数名前需要加上(::)来获取函数引用。

这个类似C++中的函数指针,然而在Python中可以直接使用函数名作为函数引用,下面是c++函数指针的例子:

#include  


 


using namespace std; 


 


void swap(int &x, int &y); 


 


int main(int arg, char* args[]) { 


    int x = 10; 


    int y = 20; 


 


    void (*methodPtr)(int &x, int &y);//声明一个函数指针 


    methodPtr = &swap; //函数指针赋值 


    methodPtr = swap;//取地址符可省略,效果和上面一致 


    methodPtr(x, y); //像给函数起了一个别名,可以直接使用()调用 


    cout << "x:" << x << " y:" << y << endl; //x:20 y:10 


} 


 


void swap(int &x, int &y) { 


    int tmp = x; 


    x = y; 


    y = tmp; 



}  

回到Kotlin,我们还可以将一个函数传递给另一个函数,比如:

//函数参数 


fun  doMap(list: List, function: (it: T) -> Any) { 


    for (item in list) { 


        function(item) 


    } 



}  

第一个参数是一个List,第二个参数是一个函数,目的就是将List中的每一个元素都执行一次第二个函数。使用方法如下:

val strList = listOf("h" ,"e", "1", "a", "b", "2", " ", "", "c", "5", "7", "F") 


doMap(strList, {item ->print("item: ${upperLetter(item)}, ") }) 


 


fun upperLetter(item: String): String { 


    if (item.isLetter()) { 


        return item.toUpperCase() 


    } 


    return item 



}  

第二个参数直接传进去了一个lambda表达式,当然也可以传一个函数引用:

val strList = listOf("h" ,"e", "1", "a", "b", "2", " ", "", "c", "5", "7", "F") 


doMap(strList, ::printUpperLetter) 


 


fun printUpperLetter(item: String) { 


    print("item: ${upperLetter(item)}, ") 


} 


 


fun upperLetter(item: String): String { 


    if (item.isLetter()) { 


        return item.toUpperCase() 


    } 


    return item 



}  

效果和上面的代码一样。

在C++中使用函数指针可以实现类似的效果:

using namespace std; 


 


void mMap(vector list, void (*fun)(int item)); 


 


int main(int arg, char* args[]) { 


    vector list = {2,3,4,3,2,1,2}; 


    mMap(list, [](int item) -> void { cout << item << endl; }); 


} 


 


void mMap(vector list, void (*fun)(int item)) { 


    for(int it : list) { 


        fun(it); 


    } 



}  

再次回到Kotlin,如果函数作为入参在入参列表的最后一个,你还可以这样做,直接写在大括号内:

fun main(args: Array) { 


    log { sum(1,2) } 


} 


 


fun  log(function: () -> T) { 


    val result = function() 


    println("result -> $result") 



}  

是不是有点像gradle配置文件的写法,所以Kotlin可以很方便的编写 领域专用语言(DSL)

另外Kotlin还支持局部函数和函数作为返回值,看下面的代码:

fun main(args: Array) { 


    val addResult = lateAdd(2, 4) 


    addResult() 


} 


//局部函数,函数引用 


fun lateAdd(a: Int, b: Int): Function0 { 


    fun add(): Int { 


        return a + b 


    } 


    return ::add 



}  

在lateAdd内部定义了一个局部函数,最后返回了该局部函数的引用,对结果使用()操作符拿到最终的结果,达到延迟计算的目的。

函数作为一级公民当然可以像普通对象一样放进map中,比如下面这样:

val funs = mapOf("sum" to ::sum) 


val mapFun = funs["sum"] 


if (mapFun != null) { 


   val result = mapFun(1,2) 


   println("sum result -> $result") 


} 


 


fun sum(a: Int, b: Int): Int { 


    return a + b 



}  

将一个函数引用作为value放进了map中,取出来之后使用()操作符调用,可以简化一些if,else的场景。

基于以上函数式编程的特性,Kotlin可以像RxJava一样很方便的进行相应式编程,比如:

fun printUpperLetter(list: List) { 


    list 


            .filter (fun(item):Boolean { 


                return item.isNotEmpty() 


            }) 


            .filter { item -> item.isNotBlank()} 


            .filter { 


                item -> 


                if (item.isNullOrEmpty()) { 


                    return@filter false 


                } 


                return@filter item.matches(Regex("^[a-z|A-Z]$")) 


            } 


            .filter { it.isLetter() } 


            .map(String::toUpperCase) 


            .sortedBy { it } 


            .forEach { print("$it, ") } 


    println() 



}  

相关推荐