Android 开发者如何函数式编程 (一)

本文讲的是Android 开发者如何函数式编程 (一),


最近我花了一些时间学习 Elixir —— 一门极好的编程语言,适合初学者入门学习。

我在想,为什么我们不在 Android 开发中使用函数式编程的思想和技术呢?

大多数人当听到函数式编程时,他们会想到 Hacker News 发布的一些关于单子、高阶函数以及抽象数据类型的内容。这好像是一个离平时辛勤编码的程序员很远的神秘领域,它仅仅属于强大的黑客们。

不去管它!我要说你也可以学它,你也可以使用它,你也可以用它打造漂亮的应用 —— 拥有优雅的、可读性强的并且错误少的代码。

欢迎阅读 Android 开发者如何函数式编程(FP)。接下来的一系列文章中,我将带领大家一起学习 FP 基础以及如何在老版本的 Java 中使用 FP。本文旨在实用性,会尽量少用学术性的言论。

FP 是一个很大的话题。我们接下来只会涉及对编写 Android 代码有用的思想和技术。由于完整性的原因大家可能会看到了一些不能直接应用的思想,但是我会尽可能的保证材料的相关性。

准备好了吗?我们开始吧。

什么是函数式编程?我为什么要用?

问得好。函数式编程是一系列被不公平对待的编程思想的保护伞。它的核心思想是,它是一种将程序看成是数学方法的求值、不会改变状态、不会产生副作用(后面我们马上会谈到)的编程方式。

FP 核心思想强调:

  • 声明式代码 —— 程序员应该关心是什么,让编译器和运行环境去关心怎样做。
  • 明确性 —— 代码应该尽可能的明显。尤其是要隔离副作用避免意外。要明确定义数据流和错误处理,要避免 GOTO 语句和 异常,因为它们会将应用置于意外的状态。
  • 并发 —— 因为纯函数的概念,大多数函数式代码默认都是并行的。由于CPU运行速度没有像以前那样逐年加快((详见 摩尔定律)), 普遍看来这个特点导致函数式编程渐受欢迎。以及我们也必须利用多核架构的优点,让代码尽量的可并行。
  • 高阶函数 —— 函数和其他的语言基本元素一样是一等公民。你可以像使用 string 和 int 一样的去传递函数。
  • 不变性 —— 变量一经初始化将不能修改。一经创建,永不改变。如果需要改变,需要创建新的。这是明确性和避免副作用之外的另一方面。如果你知道一个变量不能改变,当你使用时会对它的状态更有信心。

声明式、明确性和可并发的代码,难道不是更易推导以及从设计上就避免了意外吗?真希望已经激起了你的兴趣。

作为本系类文章的第一部分,我们从一些 FP 的基本概念开始:纯粹、副作用和排序。

纯函数

当一个函数的输出只依赖输入并且没有副作用(我们后面马上会谈到),那么这个函数就是纯函数。下面我们看一个例子。

一个简单的两数求和的函数。一个数从文件中读取,另一个数是传进来的参数。

int add(int x) {
    int y = readNumFromFile();
    return x + y;
}

这个函数的输出不仅仅依赖于输入,还依赖于 readNumFromFile() 的返回,对于相同的入参 x 可能有不同的输出。这个函数不是纯函数。

下面我们将它改为纯函数。

int add(int x, int y) {
    return x + y;
}

现在函数的输出只依赖于输入了。对于给定的 x 和 y,函数总会返回相同的输出。这个函数是纯函数。数学函数的计算与之一样,一个数学函数的输出只依赖于输入 —— 这也是为什么函数式编程更像数学,而不是我们通常使用的编程方式。

P.S. 没有输入也是一种输入。如果一个函数没有输入并且每次的返回总是相同不变的,那么它也是一个纯函数。

P.P.S. 固定输入总是返回相同输出的属性也被成为 引用透明性,当讨论纯函数时你可能会遇到这种说法。

副作用

我们修改下原来的函数来研究这个概念,我们将函数改成可以将计算结果存储到文件中。

int add(int x, int y) {
    int result = x + y;
    writeResultToFile(result);
    return result;
}

该函数将计算结果写到了一个文件中,也就是修改了外界的状态。那么该函数就是有 副作用,不再是纯函数了。

任何修改外界状态(修改变量、写文件、存储 DB、删除内容等)的代码都是有副作用的。

FP 中应该避免使用有副作用的函数,因为它们不在是纯函数而是依赖于历史上下文。代码的上下文不是由自身决定,这将导致它们更难推导。

我们假设你写了一段依赖缓存的代码,代码的输出依赖于是否有人已经对缓存做了写操作、写入了什么、什么时候写入的、写入的数据是否有效等。你无法知道你的程序在做什么,除非你知道它依赖的缓存的所有可能状态。如果你拓展代码以包括所有应用依赖的内容 —— 网络、数据库、文件、用户输入等等,那么会变得很难确切的知道正在发生什么,以及很难一次性将所有内容都考虑到。

这是否意味着我们不使用网络、数据库和缓存了?当然不是。当执行结束之后,应用往往需要做些什么。以 Android 应用为例,往往是更新 UI 以便用户从我们的应用中真正地获得有用的内容。

FP 最伟大的概念并非完全的放弃副作用,而是包容、隔离它们。我们将副作用置于系统的边缘,尽可能减少影响,使得应用更易懂,避免有副作用的函数将应用弄得一团糟。在本系列后面的文章中,研究应用的函数式架构时,我们会具体的讨论这个问题。

排序

如果我们有几个没有副作用的纯函数,那么它们的执行顺序是无关紧要的。

我们看个例子,我们有一个函数,函数会调用 3 个纯函数:

void doThings() {
    doThing1();
    doThing2();
    doThing3();
}

我们明确的知道这些函数互不依赖(因为一个函数的输出不是另一个的输入)并且我们知道它们不会改变系统的任何内容(因为它们是纯函数)。这样它们的执行顺序是完全可交换的。

独立的纯函数的执行顺序是可重排序和优化的。需要注意的是,如果 doThing1() 的结果是 doThing2() 的输入,那么它们需要按顺序执行,但是 doThing3() 依然可以重排序在 doThing1() 之前执行。

可重排序的特性对我们来说有什么益处?当然是并发了。我们可以在 3 个 CPU 上分别运行它们,而不需要担心发生任何问题。

多数情况下,像 Haskell 这样高级纯函数式语言的编译器中,可以通过分析你的代码判断是否可并行,可以防止你出现搬起石头砸自己的脚的事情(比如死锁、条件竞争等)。这些编译器理论上可以自动并行化你的代码(虽然据我所知目前编译器都不支持,但是相关的研究正在进行)。

尽管你的编译器并不像上面说的那样,但单作为一个程序员,有能够根据函数的签名判断代码是否可并行,并且避免代码存在隐性副作用而导致线程问题的能力还是很重要的。

总结

希望第一本分已经激起了你对 FP 的兴趣。纯粹性、无副作用的函数是的代码更易读并且是实现并行的第一步。

在我们开始实现并行之前,我们需要了解下 不变性。在本系列文章的第二部分将进行探讨,并且可以看到在不需要借助锁和互斥变量的情况下,纯函数和不变性是如何帮助我们编写简单易懂的可并行代码的。






原文发布时间为:2017年3月03日


本文来自合作伙伴掘金,了解相关信息可以关注掘金网站。

时间: 2024-09-30 05:34:17

Android 开发者如何函数式编程 (一)的相关文章

给 JavaScript 开发者讲讲函数式编程

和大多数人一样,我在几个月前听到了很多关于函数式编程的东西,不过并没有更深入的了解.于我而言,可能只是一个流行词罢了.从那时起,我开始更深地了解函数式编程并且我觉得应该为那些总能听到它但不知道究竟是什么的新人做一点事情. 谈及函数式编程,你可能会想到它们:Haskell 和 Lisp,以及很多关于哪个更好的讨论.尽管它们都是函数式语言,不过的确有很大的不同,可以说各有各的卖点.在文章的结尾处,我希望你能够对这些有一个更加清晰的认识.它们都在某些更加现代的语言上留下了自己的影子.你可能听说过这样两

Android 开发者如何使用函数式编程 (二)

本文讲的是Android 开发者如何使用函数式编程 (二), 如果你没有读过第一部分,请到这里读: Android 开发者如何使用函数式编程 (一) 在上一篇帖子中,我们学习了纯粹性*.副作用和排序**.在本部分中,我们将讨论不变性和并发. 不变性 不变性是指一旦一个值被创建,它就不可以被修改. 假设我有一个像这样的 Car 类: public final class Car { private String name; public Car(final String name) { this.

函数式接口、默认方法、纯函数、函数的副作用、高阶函数、可变的和不可变的、函数式编程和 Lambda 表达式 - 响应式编程 [Android RxJava2](这到底是什么)第三部分

本文讲的是函数式接口.默认方法.纯函数.函数的副作用.高阶函数.可变的和不可变的.函数式编程和 Lambda 表达式 - 响应式编程 [Android RxJava2](这到底是什么)第三部分, 太棒了,我们又来到新的一天.这一次,我们要学一些新的东西让今天变得有意思起来. 大家好,希望你们都过得不错.这是我们的 RxJava2 Android 系列的第三篇文章. 第一部分 第二部分 在这篇文章中,我们将讨论函数式的接口,函数式编程,Lambda 表达式以及与 Java 8 的相关的其它内容.这

Android 开发者自述:为什么我要改用 Kotlin?

写在前面的话,作为一个不熬夜的人,一觉醒来发现 Kotlin 成为了 Android 的官方语言,可谓是大喜过望.为了趁热打铁,我决定提前三天放出原定本周日 Release 的文章.希望能及时让大家了解一下 Kotlin. 相信很多开发人员,尤其是 Android 开发者都会或多或少听说过 Kotlin,当然如果没有听过或者不熟悉也没有关系.因为本篇文章以及博客后期的内容会涉及到很多关于 Kotlin 的知识分享. 在写这篇文章前的一个多月,Flipboard 中国的 Android 项目确定了

(转)现代C++函数式编程

本文转自:http://geek.csdn.net/news/detail/96636     现代C++函数式编程 C++ 函数式编程 pipeline 开发经验 柯里化 阅读2127    作者简介: 祁宇,武汉烽火云创软件技术有限公司研发中心技术总监,<深入应用C++11>作者,C++开源社区purecpp.org创始人,致力于C++11的应用.研究和推广.乐于研究和分享技术,爱好C++,爱好开源. 导读: 本文作者从介绍函数式编程的概念入手,分析了函数式编程的表现形式和特性,最终通过现

基于范型的java函数式编程(一)

编程|函数 注:在您阅读本篇的时候,希望你对Java Generic(范型)能够有所了解和明白. 记:周末在给javaparty讲FP中,很多人似乎对fp并不关心,也认为java中fp的作用不大.其实这是个很大的观念错误,范型的发展,对java的函数式编程支持很大,对Functor的影响也非常大.Functor在算法.逻辑.条件计算.规则引擎等等方面,都会有很大的作为,这个影响可就会深远的多了.-- 估且以此篇的开端,唤醒java开发者对FP in Java的重新认识. 周六给javaparty

浅谈Java 8的函数式编程

关于"Java 8为Java带来了函数式编程"已经有了很多讨论,但这句话的真正意义是什么? 本文将讨论函数式,它对一种语言或编程方式意味着什么.在回答"Java 8的函数式编程怎么样"之前,我们先看看Java的演变,特别是它的类型系统,我们将看到Java 8的新特性,特别是Lambda表达式如何改变Java的风景,并提供函数式编程风格的主要优势. 函数式编程语言是什么? 函数式编程语言的核心是它以处理数据的方式处理代码.这意味着函数应该是第一等级(First-cla

Fn.py:享受Python中的函数式编程

尽管Python事实上并不是一门纯函数式编程语言,但它本身是一门多范型语言,并给了你足够的自由利用函数式编程的 便利.函数式风格有着各种理论与实际上的好处(你可以在Python的文档中找到这个列表): 形式上可证 模块性 组合性 易于调试及测试 虽然这份列表已经描述得够清楚了,但我还是很喜欢Michael O.Church在他的文章"函数式程序极少腐坏(Functional programs rarely rot)"中对函数式编程的优点所作的描述.我在PyCon UA 2012期间的讲

函数式编程系列 (一)

FP是如何增强代码可读性的 本文将介绍使用 Stream实现 groupBy 的功能 在平时的开发中,如果你曾经用过 Map<U,List<V>>或者 guava的Multimap,那么恭喜你,在 Java8下你可以更加轻松地实现同样的功能. 为了与我们的日常工作结合的更加紧密,本文中的代码片段都出自 buy系统: 案例一 遍历一个 List<T>,然后根据 T 的某个属性(类型 U)生成一个 Map<U,List<T>> public Map&