Kotlin 函数与函数式编程浅析

338次阅读  |  发布于3年以前

如果你对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++函数指针的例子:

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

上面的代码只是做演示,并无实际意义。具体语法请查看官方文档。我相信Kotlin作为一种强类型的现代化语言可以在保证稳定性的同时极大地提高开发者的开发效率。

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8