Android Kotlin函数式编程(I):释放函数式编程的洪荒之力

#Android函数式编程(I):释放函数式编程的洪荒之力#

原文来自:系列第一部分:释放函数范式的洪荒之力

当我们在合理地使用函数式编程特性的时候,他可以成为我们的强悍的助力,尽管Java 8包含了一些函数式工具,你也能想象得到Android开发者不会很快地用上(甚至不支持)。所以有很多(JVM平台上)可以替换的语言开始尝试解决这个问题。

译者注:前几天Google破天荒的提前在Google IO发布会之前发布了Android N预览版,并且发布了Jack编译器,
宣布支持OpenJSD提供的Java 8特性的编译并且向下兼容至Android 2.3,译者暂时还没有在Android上尝试
Java 8和全新的Jack编译器。相信Google不我欺。

现代语言的函数式编程

函数式编程语言依赖于函数和不变性以达到对函数的调用总是返回同类结果。通常情况下,许多现代编程语言做了完美的折中,例如Kotlin和Scala,将过程式和函数式结合起来,用以在单一语言中提供这两种强有力的思维理念。其他的语言要不更加偏向于函数式编程,有的直接或者十分贴合过程式语言。

在Android开发中使用Lambda表达式

一个lambda表达式是一种定义匿名函数的简单方式。lambda表达式非常有用,可以省掉我们在实现一个抽象类或者接口中必须书写的特定的模版代码。在Kotlin中,我们可以用用一个函数作为另一个函数的参数。比如,一个函数需要一个回调,我们可以如下简化定义:

fun runAsync(callback: () -> Unit){
    ...
    callback()
}

而且使用时也非常直白。在一些变换之后,对这个函数的调用可以简化成如下:

runAsync { toast("Finished") }

Kotlin中还有个更酷的特性,它允许我们我们写单函数接口的好像他们是一个lambda表达式,如此来极大地简化我们的代码。我们可以很容易地看到一个例子。想象一下我们想要在View中写个典型的setOnClickListener()。 在Java中的接口定义如下:

public interface OnClickListener {
    void onClick(View v);
}

然后,我们需要写一个匿名类来实现这个接口:

view.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        Toast.makeText(v.getContext(), "Click", Toast.LENGTH_SHORT).show();
    }
});

这部分代码可以在Kotlin中变换成(使用了Anko toast函数):

view.setOnClickListener(object : OnClickListener {
    override fun onClick(v: View) {
        toast("Click")
    }
})

正如我之前提到的,Kotlin允许一些经由Java库的优化,还有任何接收一个单函数接口的实现都可以用函数在替代。如果我们要顶一一个函数setOnClickListener(),那么我们可以用如下代码:

fun setOnClickListener(listener: (View) -> Unit)

一个lambda表达式由限定在箭头的左侧的该函数的参数(由小括号括起来),以及箭头右侧的返回类型。在这个情况下,我们可以获得View参数值并且返回Unit类型(无返回)(译者注:同Java的void)。因此,考虑到这一点,我们可以再将前面的代码简化一点点:

view.setOnClickListener({ view -> toast("Click")})

真是个不错的变化!当我们定义一个函数的时候,我们一定要用括号括起来,然后箭头左边指定函数的参数值,然后右边是要执行的代码。如果左边的参数值不用的话,我们可以甚至连左边的部分也省略掉:

view.setOnClickListener({ toast("Click") })

如果作为参数的函数是最后一个参数,那么我们还可以把它移到括号外:

view.setOnClickListener() { toast("Click") }

最后,如果函数是唯一的参数,我们可以省掉小括号:

view.setOnClickListener { toast("Click") }

这可比Java缩减了5倍的源代码,也更易于理解代码做了什么。这非常让人印象深刻。Anko给我们提供了包括用扩展方法的方法简化的版本。之前也展示过的方法:

view.onClick { toast("Click") }
译者注:
1、这一部分的对匿名函数的简化方式与Groovy十分相像,几乎一样,不知道是否
其他的函数式语言的lambda表达式的简化方式是不是也是这样。译者认为,将以代
码块代表的函数参数放到小括号外边,Kotlin采用的也是和Groovy相同的设计,
在Groovy里十分容易和普通的函数定义产生混淆感,对语法的理解产生一定的难度。
而Kotlin对函数的定义是才用和Scala类似的Pascal式定义,虽然消除了像在
Groovy中的混淆感,但也是不那么容易让人觉得更有易读性,习惯于Java语言的
开发者阅读到这部分代码时,我觉得或多或少会需要在脑海中脑补一下,这个代码
块实际上是作为函数参数传入的。个人认为,为了易读性,而不是DSL的思维来的
话,建议在合作开发中将作为参数的函数代码块放到函数的小括号内更加易于理解。

2、读者在阅读本文时会有一种漫天都是在围绕着“函数”的感觉。我想说,你的感
觉是对的,函数式编程范式里,函数是首要公民,面向对象的特性也要通过设计模
式或者匿名函数等方式组合成函数的范式。译文中会有在一句话中提到好几次函数
这个词,有时分不清哪个指的是调用的函数本身,哪个是作为参数的函数,甚至有
的需要脑补到作为面向对象语言时单函数接口的定义的函数。这些都要在实践中细
细品味琢磨。

3、上面的译文很短,但是我写的译者注很长,这是为了之前没有接触过函数式编
程的读者提供一点背景资料,便于Google寻找资料。

4、再注译者注:Scala可以说语法是个大杂烩。变量的定义格式是参照Pascal来的。
Pascal的变量定义格式:var i:integer,
模式: [var关键字] [变量名]:[变量类型]
我们可以看出来,Kotlin,Scala的变量定义都是照着这个范式来的,而我们用的
企业中多数语言是C语言定义变量的模式来的:
[变量类型] [变量名]
当然,这个问题确实也和函数式编程没有什么关系。

5、题外话:一谈到lambda就有说不完的话。从语言层面讨论,也有谈不完的话。
Kotlin的诞生和Kotlin多范式,对Java的语言进行了扩展,而又没有替代Java
API的野心,我相信,Kotlin的诞生应该是业界的一种呼声和对多范式语言的需求。
很多人包括我在内,依然还在使用陈旧的Java 6的语言范式当中。Java的好处和弊
端也都能管中窥豹。Java 6作为一代经典始终坚挺,Java 7作为锦上添花。但是响
应式和并发式的编程地流行,Java 6和Java 7已然像是迟暮老者,匿名函数、扩展
方法等更加方便和灵活的语言特性呼声高涨。我想这就是Kotlin诞生的原因吧,
Java有的它天生自带,Java没有的它也都有。它有天生的老师Scala作为参照,官
方声称比Scala更加简洁。而我理解官方所说的简洁,不是Kotlin代码的易读和精
简,而是语言更加具体,将原本Java语言里隐式的约定都体现在显式的代码中,我
认为Kotlin与其用简洁来形容不如用“明确”更贴切。Kotlin是个对业务对逻辑更
加“明确”的语言,我希望让读者明白,Kotlin简洁,不简单,我相信它的血统来自
于Scala。然而Scala,它更像是想当一个超级多面手,想当中国高考式的学霸。

高级用法:扩展语言

多亏了这些变化,我们可以创造我们自己的构建者和代码块。Kotlin提供的标准库有一些有趣的功能,如:with。有个更简单的实现:

inline fun <T> with(t: T, body: T.() -> Unit) { t.body() }

这个函数给类型T一个对象,并且一个方法将用于扩展方法。实现只要获得对象并且执行这个函数就可以了。作为该函数的第二个参数是另一种函数,它是可以被挪出括号的,因此我们可以创建一个代码块,我们可以用this就可以直接使用这个对象的公共属性和函数了:

with(forecast) {
    Picasso.with(itemView.ctx).load(iconUrl).into(iconView)
    dateView.text = date
    descriptionView.text = description
    maxTemperatureView.text = "${high.toString()}º"
    minTemperatureView.text = "${low.toString()}º"
    itemView.onClick { itemClick(forecast) }
}

结论

lambda表达式的力量只是被我们的思维局限了。想要获得它蕴藏的巨大力量,需要我们勤加练习函数式编程,它定会不负如来不负卿。

译者后记

antonioleiva.com提供的两篇关于函数式编程的文章都翻译完了。我是先翻译了II后翻译了I,
翻译当中难免会有一些拗口的地方和不符合国人阅读的习惯因此有些词句在上下文的基础上进行了
一定程度的引申。至于Kotlin语言本身,前一阵子因为工作比较忙的原因,一直没有拿出时间去
勤加练习这门后起之秀。国内已经有一些社区再做Kotlin的中文doc项目了,而我向来独来独往,
不善于召集支援者,想为个上百页的文档翻译真的是心有余而力不足。不过在下也有贡献之心,也
偶然发现了国外写《Kotlin for Android Developers》这本书的大神的博客,所以这次拿出
一点时间来,翻译一下这两篇可以对Kotlin当中关于函数式编程特性的补漏的文章,是我个人的一
份阅读笔记,也算是我回馈开源社区的一点力量和一份心意。