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() }