V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
pqpo
V2EX  ›  Kotlin

Kotlin 函数与函数式编程

  •  1
     
  •   pqpo · 2017-05-25 19:48:59 +08:00 · 5250 次点击
    这是一个创建于 2500 天前的主题,其中的信息可能已经有所发展或是发生改变。

    原文链接:https://pqpo.me/2017/05/25/kotlin-function/

    自从 Google 爸爸宣布 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++函数指针的例子:

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

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

    3 条回复    2017-05-26 13:18:59 +08:00
    KeepPro
        1
    KeepPro  
       2017-05-26 01:08:12 +08:00 via Android
    感谢楼主科普(安利)
    sagaxu
        2
    sagaxu  
       2017-05-26 08:45:34 +08:00
    pqpo
        3
    pqpo  
    OP
       2017-05-26 13:18:59 +08:00
    @sagaxu 很生动
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2722 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 15:08 · PVG 23:08 · LAX 08:08 · JFK 11:08
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.