Kotlin学习系列——函数式编程

一、什么是Kotlin?

Kotlin是一门新兴的Jvm语言,与Scala等充满野心,想要取代Java的Jvm语言不同,Kotlin更像是Java的扩展,它能很好的和已有的Java代码一起工作,而比起Java,Kotlin提供了许多能够大幅提高开发效率的特性,使用Kotlin能写出比Java表现力更强,且更安全的代码。并且,Kotlin对函数式编程提供了比Java8更好的支持。
就在今年,Google宣布使用Kotlin作为安卓端的第二门官方编程语言,可以说,依托于巨大的安卓市场,Kotlin的前景是十分广阔的。

二、什么是函数式编程?

函数式编程是不同于过程式编程的另一种编程范式(现在流行的面向对象编程,实际上是过程式编程的一种编程思想)。函数式编程的思想在许多方面和过程式是冲突的,比如,过程式编程倾向于描述“怎么做”,而函数式编程则更倾向于描述“做什么”,过程式倾向于使用变量,而函数式则倾向于使用常量。尽管如此,函数式和过程式依旧是可以共存的。
相比过程式编程,函数式编程具有许多优势,比如:
· 代码更简洁
· 代码更容易推理
· 代码可复用性更强,API更灵活
· 函数式在编写多线程程序时更加容易
当然,相对的,函数式编程也具有一些缺点:
· 效率相对过程式较低
· 对于没有接触过函数式编程的程序员,函数式的代码就像文言文一样晦涩难懂
下面通过一个简单的例子:列表求和来比较一下过程式编程和函数式编程:
(实际上,Kotlin已经为数字列表扩展了sum方法,但为了方便演示,我们重新实现一边算法)

// 过程式代码:
fun main(args: Array<String>) {
    val ints = listOf(1, 2, 3, 4, 5)
    var sum = 0
    for (i in ints) sum += i
    sum.log() // Log 15
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

注释:
fun(function)关键字用于定义一个函数,在Kotlin中,函数可以定义在任意位置,因此main函数也不需要被包裹在类里面。
val(value)关键字定义一个不可变的变量,使用val关键字定义的变量不可以重新赋值。Kotlin具有类型推导的功能,如果变量的类型可以通过=右边的表达式推测出来,就可以省略变量的类型声明。
listOf函数用于构造一个不可变列表,不可变的数据结构不提供增删改的操作。
var(variable)关键字用于定义一个可变的变量。
for in循环遍历ints中的每一个元素,依次将元素赋值给i
.log()是一个自定义的扩展方法,用于将表达式的值呈现在控制台:

fun Any.log() = println(this)
 
 
  • 1

我们再来看一段等效的函数式代码:

// 函数式代码:
fun main(args: Array<String>) {
    val ints = listOf(1, 2, 3, 4, 5)
    ints.fold(0){ a, b -> a + b }.log()
    // Log 15
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

函数式的代码更加简洁一些,并且没有定义sum变量,而是使用fold方法对列表进行求和。fold方法对列表进行折叠,它接受两个参数,第一个参数是初始值,第二个参数是一个函数。这里我们使用了Lambda表达式,Lambda表达式是一个匿名函数,在这里,它等效于:

// 函数体只有一条语句时,包裹函数体的花括号可以简化成等号
fun plus(a: Int, b: Int): Int = a + b
 
 
  • 1
  • 2

像fold这样接受另一个函数作为参数,或是返回一个函数的函数,我们称之为“高阶函数”,在Kotlin中,如果要传入一个Lambda表达式给一个函数的最后一个参数,那么该Lambda表达式可以被放置在函数调用的小括号外侧。
fold方法遍历整个列表,不断将列表中的元素与初始值“结合”,而结合的方法就是我们提供的Lambda,即将两个整数相加:
这里写图片描述
从图中可以看出,fold采用的算法其实和过程式直接用循环是一样的。
从这个简单的例子中,我们大概能看出一丝函数式编程的风格,但仍然没有体现出函数式编程的优势。我们再通过一个更加复杂的例子来观察一下:

// 过程式代码:
fun main(args: Array<String>) {
    val list = listOf(
            listOf(1, 2, 3),
            listOf(4, 5, 6),
            listOf(7, 8, 9)
    )
    // 求出每一个子列表的乘积,再求和
    var sum = 0
    for (sub in list) {
        var product = 1
        for (i in sub) product *= i
        sum += product
    }
    sum.log() // Log 630
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

似乎代码变得复杂了起来,我们必须读完整个循环才能明白程序的意图。那么函数式如何呢:

// 函数式代码:
fun main(args: Array<String>) {
    val list = listOf(
            listOf(1, 2, 3),
            listOf(4, 5, 6),
            listOf(7, 8, 9)
    )
    list.map { it.fold(1){ a, b -> a * b } }
            .fold(0){ a, b -> a + b }
            .log() // log 630
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

这里调用了map方法。map方法将函数应用到列表的每一个元素上,从而转化整个列表。再来观察一下这个Lambda表达式:

{ it.fold(1){ a, b -> a * b } }
 
 
  • 1

在Kotlin中,如果一个Lambda表达式只有一个参数,那么可以使用it关键字来指代这个参数,从而省略参数列表。在这里,it指代的是list中的子列表。我们对每一个子列表都采用乘法进行折叠。由于折叠之后,整个列表被折成了一个整数,所以在调用map之后,list从一个嵌套的二维列表变成了一个一维的列表。
第二个fold则和第一个例子中对整数求和的fold方法一模一样。
这么写的好处是显而易见的:在过程式的代码中,我们再阅读循环时,必须追踪其中的每一个变量(sum, product, sub 和 i)才能弄明白程序的意图,而函数式的代码则不依赖任何外部变量和上下文,每条语句要表达的计算已经完全包含在这条语句中了。
你可能会说,过程式代码容易debug,比如,我们想要查看每一个子表达式的乘积,只需要条件一条语句:

...
    for (sub in list) {
        var product = 1
        for (i in sub) product *= i
        product.log()
        sum += product
    }
...
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

事实上,函数式的代码同样可以实现,并且不需要破坏现有的调用链:

...
    list.map { it.fold(1){ a, b -> a * b } }
            .apply { log() }
            .fold(0){ a, b -> a + b }
            .log() // log 630
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

apply方法比较特殊,obj.apply{ log() }相当于调用了:

fun <T> f(obj: T): T {
    obj.log()
    return obj
}
 
 
  • 1
  • 2
  • 3
  • 4

它可以在不破坏调用链的情况下调用一个对象上的返回值为Unit的方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值