android 函数式编程

by Anup Cowkur

通过安纳普·考库(Anup Cowkur)

(Functional Programming for Android Developers — Part 2)

In the last post, we learned about Purity, Side effects and Ordering. In this part, let’s talk about immutability and concurrency.

在上一篇文章中,我们了解了纯度 , 副作用和订购。 在这一部分中,让我们讨论不变性和并发性 。

If you haven’t read part 1, please read it here:

如果您尚未阅读第1部分,请在此处阅读:

Functional Programming for Android developers — Part 1Lately, I’ve been spending a lot of time learning Elixir — An awesome functional programming language that is friendly…medium.com

适用于Android开发人员的函数式编程—第1部分 最近,我花了很多时间学习Elixir —一种很棒的函数式编程语言,非常友好。

(Immutability)

Immutability is the idea that a value once created can never be modified.

不变性是一个想法,一旦创建就永远不能修改它。

Let’s say I have an Car class like this:

假设我有一个汽车课 像这样:

Java

Java

public final class Car {    private String name;    public Car(final String name) {        this.name = name;    }    public void setName(final String name) {        this.name = name;    }    public String getName() {        return name;    }}

Kotlin

Kotlin

class Car(var name: String?)

Since it has a setter in Java and is a mutable property in Kotlin, I can modify the name of the car after I’ve constructed it:

由于它在Java中具有二传手,并且在Kotlin中是可变属性,因此我可以在构造汽车后修改其名称:

Java

Java

Car car = new Car("BMW");car.setName("Audi");

Kotlin

Kotlin

val car = Car("BMW")car.name = "Audi"

This class is not immutable. It can be modified after creation.

此类不是一成不变的。 创建后可以对其进行修改。

Let’s make it immutable. In order to do that in Java, we have to:

让我们使其不变。 为了在Java中做到这一点,我们必须:

  • Make the name variable final. 将名称变量定为final。
  • Remove the setter.
  • Make the class final as well so that another class cannot extend it and modify it’s internals. 也使该类为final ,以便另一个类不能扩展它并修改其内部。

Java

Java

public final class Car {    private final String name;    public Car(final String name) {        this.name = name;    }    public String getName() {        return name;    }}

In Kotlin, we just have to make the name an immutable value.

在Kotlin中,我们只需要使该名称成为不变值即可。

Kotlin

Kotlin

class Car(val name: String)

Now if someone needs to create a new car, they need to initialize a new object. No one can modify our car once it’s created. This class is now immutable.

现在,如果有人需要创建新车,则需要初始化一个新对象。 一旦创建我们的汽车,没有人可以修改。 该类现在是不可变的 。

But what about the getName() getter in Java or the name accessor in Kotlin? It’s returning our name variable to the outside world right? What if someone were to modify the name value after getting a reference to it from this getter?

但是Java中的getName() getter或Kotlin中的名称访问器呢? 它会将我们的名称变量返回给外部世界,对吗? 如果有人从此吸气剂获得对名称值的引用后修改了该怎么办?

In Java, strings are immutable by default. Even if someone got a reference to our name string and were to modify it, they would get a copy of the name string and the original string would remain intact.

在Java中, 字符串默认是不可变的 。 即使有人引用了我们的名称字符串并对其进行了修改,他们也将获得名称字符串的副本,并且原始字符串将保持不变。

But what about things that are not immutable? A list perhaps? Let’s modify the Car class to have a list of people who drive it.

但是那些不是一成不变的东西呢? 清单吗? 让我们修改Car类,以列出驾驶它的人的列表。

Java

Java

public final class Car {    private final List<String> listOfDrivers;    public Car(final List<String> listOfDrivers) {        this.listOfDrivers = listOfDrivers;    }    public List<String> getListOfDrivers() {        return listOfDrivers;    }}

In this case, someone can use the getListOfDrivers() method to get a reference to our internal list and modify it thus rendering our class mutable.

在这种情况下,某人可以使用getListOfDrivers()方法获取对我们内部列表的引用并对其进行修改,从而使我们的类可变 。

To make it immutable, we must pass a deep copy of the list in the getter that is separate from our list so that the new list can be safely modified by the caller. We must also make a deep copy of the list that is passed in to our constructor so that no one can modify it externally after the car is constructed.

为了使其不可变,我们必须在与我们的列表分开的getter中传递列表的深层副本,以便调用者可以安全地修改新列表。 我们还必须复制传递给构造函数的列表的深层副本,以便在构造汽车后没有人可以在外部对其进行修改。

A deep copy means that we copy all the dependent data recursively. For instance, if the list was a list of Driver objects instead of just plain strings, we’d have to copy each of the Driver objects too. Otherwise, we’d be making a new list with references to the original Driver objects which could be mutated. In our class, since the list is composed of immutable strings, we can make a deep copy like this:

深层复制意味着我们递归复制所有相关数据。 例如,如果该列表是Driver对象的列表,而不仅仅是纯字符串,则我们也必须复制每个Driver对象。 否则,我们将创建一个新列表,并引用可能会发生变化的原始Driver对象。 在我们的课程中,由于列表由不可变的字符串组成,因此我们可以像这样进行深层复制:

Java

Java

public final class Car {    private final List<String> listOfDrivers;    public Car(final List<String> listOfDrivers) {        this.listOfDrivers = deepCopy(listOfDrivers);    }    public List<String> getListOfDrivers() {        return deepCopy(listOfDrivers);    }    private List<String> deepCopy(List<String> oldList) {        List<String> newList = new ArrayList<>();        for (String driver : oldList) {            newList.add(driver);        }        return newList;    }}

Now this class is truly immutable.

现在这堂课确实是一成不变的 。

In Kotlin, we can simply declare the list immutable in our class definition itself and then it’s safe to use (unless you call it from Java and some other edge cases like that)

在Kotlin中,我们可以简单地在类定义中声明列表为不可变的,然后可以安全使用(除非您从Java和类似的其他边缘情况中调用它)

Kotlin

Kotlin

class Car(val listOfDrivers: List<String>)

(Concurrency)

Okay, so immutability is cool and all but why bother? As we talked about in part 1, pure functions allow us easy concurrency and if an object is immutable, it’s very easy to use in pure functions since you can’t modify it and cause side effects.

好的, 不变性是很酷的,但为什么要打扰呢? 正如我们在第1部分中讨论的那样,纯函数使我们易于并发,并且如果对象是不可变的,则在纯函数中使用它非常容易,因为您不能修改它并引起副作用。

Let’s see an example. Suppose that we add a getNoOfDrivers() method in to our Car class in Java and we also make it mutable in both Kotlin and Java by allowing an external caller to modify the number of drivers variable like this:

让我们来看一个例子。 假设我们通过允许外部调用者修改这样的驱动变量的数量我们在Java中汽车类添加getNoOfDrivers()方法,我们也使它在这两个Kotlin和Java 可变 :

Java

Java

public class Car {    private int noOfDrivers;    public Car(final int noOfDrivers) {        this.noOfDrivers = noOfDrivers;    }    public int getNoOfDrivers() {        return noOfDrivers;    }    public void setNoOfDrivers(final int noOfDrivers) {        this.noOfDrivers = noOfDrivers;    }}

Kotlin

Kotlin

class Car(var noOfDrivers: Int)

Suppose we share an instance of this Car class across 2 threads: Thread_1 and Thread_2. Thread_1 wants to do some calculation based on the number of drivers so it calls getNoOfDrivers() in Java or accesses the noOfDrivers property in Kotlin. Meanwhile Thread_2 comes in and modifies the noOfDrivers variable. Thread_1 does not know about this change and happily carries on with it’s calculations. These calculations would be wrong since the state of the world has been modified without by Thread_2 without Thread_1 knowing about them.

假设我们在两个线程( Thread_1和Thread_2)上共享这个Car类的实例。 Thread_1希望根据驱动程序的数量进行一些计算,因此它在Java中调用getNoOfDrivers()或在Kotlin中访问noOfDrivers属性。 同时, Thread_2进入并修改了noOfDrivers变量。 Thread_1不了解此更改,因此很高兴继续进行计算。 这些计算将是错误的,因为在没有Thread_1的情况下Thread_2修改了世界的状态。

The following sequence diagram illustrates the issue:

以下序列图说明了该问题:

This is a classic race condition known as the Read-Modify-Write problem. The traditional way to solve this is to use locks and mutexes so that only a single thread can operate on shared data at a time and let go of the lock once the operation is complete (In our case, Thread_1 would hold a lock on Car until it completes it’s calculation).

这是一个经典的竞争条件,称为“读取-修改-写入”问题。 解决此问题的传统方法是使用锁和互斥锁,以便一次只有一个线程可以对共享数据进行操作,并在操作完成后放开该锁(在我们的示例中, Thread_1会在Car上保持锁,直到它完成了计算)。

This type of lock-based resource management is notoriously hard to do safely and leads to concurrency bugs that are extremely difficult to analyze. Many programmers have lost their sanity to deadlocks and livelocks.

众所周知,这种基于锁的资源管理很难安全地进行,并且会导致并发错误,这些错误非常难以分析。 许多程序员因死锁和活锁而失去理智。

How would immutability fix this you say? Let’s make Car immutable again:

不变性将如何解决您所说的问题? 让我们再次使Car不可变:

Java

Java

public final class Car {    private final int noOfDrivers;    public Car(final int noOfDrivers) {        this.noOfDrivers = noOfDrivers;    }    public int getNoOfDrivers() {        return noOfDrivers;    }}

Kotlin

Kotlin

class Car(val noOfDrivers: Int)

Now, Thread_1 can carry out it’s calculations without worry since it’s guaranteed that Thread_2 cannot modify the car object. If Thread_2 wants to modify Car, then it’ll create it’s own copy to do so and Thread_1 will be completely unaffected by it. No locks necessary.

现在, 由于可以保证Thread_2不能修改汽车对象,因此Thread_1可以轻松进行计算。 如果Thread_2想要修改Car,那么它将创建自己的副本来这样做,而Thread_1将完全不受它的影响。 无需锁。

Immutability ensures that shared data is thread-safe by default. Things that should not be modified cannot be modified.

不变性确保共享数据默认情况下是线程安全的。 不应修改的内容无法修改。

(What If we need to have global modifiable state?)

To write real world applications, we need shared modifiable state in many instances. There might a genuine requirement to update noOfDrivers and have it reflect across the system. We’ll deal with situations like that by using state isolation and pushing side effects to the edges of our system when we talk about functional architectures in an upcoming chapter.

要编写现实世界的应用程序,我们需要在许多情况下共享可修改的状态。 可能真正需要更新noOfDrivers并使其反映在整个系统中。 在下一章中讨论功能体系结构时,我们将通过使用状态隔离并将副作用推到系统边缘来处理此类情况。

(Persistent data structures)

Immutable objects may be great, but if we use them without restraint, they will overload the garbage collector and cause performance issues. Functional programming also provides us specialized data structures to use immutability while minimizing object creation. These specialized data structures are known as Persistent Data Structures.

不可变的对象可能很棒,但是如果我们不加限制地使用它们,它们将使垃圾收集器超载并导致性能问题。 函数式编程还为我们提供了专用的数据结构,以使用不变性,同时最大程度地减少了对象的创建。 这些专门的数据结构称为持久性数据结构。

A persistent data structure always preserves the previous version of itself when it is modified. Such data structures are effectively immutable, as their operations do not (visibly) update the structure in-place, but instead always yield a new updated structure.

持久数据结构在修改后始终会保留其自身的先前版本。 这样的数据结构实际上是不可变的,因为它们的操作不会(可视地)就地更新结构,而是始终产生新的更新结构。

Let’s say we have the following strings that we want to store in memory: reborn, rebate, realize, realizes, relief, red, redder.

假设我们要在内存中存储以下字符串: 重生,折扣,实现,实现,缓解,红色,红色。

We can store them all separately but that would take more memory than we need. If we look closely, we can see that these strings have many characters in common and we could represent them in a single trie data structure like this (not all tries are persistent but tries are one of the tools we can use to implement persistent data structures) :

我们可以将它们全部单独存储,但是这将占用比我们所需更多的内存。 如果仔细观察,我们会发现这些字符串有许多共同的字符,并且可以在这样的单个trie数据结构中表示它们(并非所有尝试都是持久性的,但是trys是我们可以用来实现持久性数据结构的工具之一):

This is the basic idea behind how persistent data structures work. When a new string is to be added, we simply create a new node and link it in the appropriate place. If an object which is using this structure needs to delete a node, we simply stop referencing it from that object but the actual node is not deleted from memory thus preventing side effects. This ensures that other objects that are referencing this structure can continue to use it.

这是持久性数据结构如何工作的基本思想。 当要添加新字符串时,我们只需创建一个新节点并将其链接到适当的位置即可。 如果使用此结构的对象需要删除节点,我们只需停止从该对象引用它,但不会从内存中删除实际节点,从而避免了副作用。 这样可以确保引用此结构的其他对象可以继续使用它。

When no other object is referencing it, we can GC the whole structure to reclaim memory.

当没有其他对象引用它时,我们可以对整个结构进行GC回收内存。

Persistent data structures in Java are not a radical idea. Clojure is a functional language that runs on the JVM and has an entire standard library of persistent data structures. You could directly use the Clojure’s standard lib in Android code but it has significant size and method count. A better alternative I’ve found is a library called PCollections. It has 427 methods and 48Kb dex size which makes it great for our purposes.

Java中的持久性数据结构并不是一个激进的想法。 Clojure是一种在JVM上运行的功能性语言,并具有完整的持久性数据结构标准库。 您可以在Android代码中直接使用Clojure的标准库,但是它具有很大的大小和方法数量。 我发现一个更好的替代方法是一个名为PCollections的库。 它具有427种方法和48Kb dex大小 ,非常适合我们的目的。

As an example, here’s how we’d create and use a persistent linked list using PCollections:

例如,这是我们使用PCollections创建和使用持久链表的方法:

Java

Java

ConsPStack<String> list = ConsPStack.empty();System.out.println(list);  // []ConsPStack<String> list2 = list.plus("hello");System.out.println(list);  // []System.out.println(list2); // [hello]ConsPStack<String> list3 = list2.plus("hi");System.out.println(list);  // []System.out.println(list2); // [hello]System.out.println(list3); // [hi, hello]ConsPStack<String> list4 = list3.minus("hello");System.out.println(list);  // []System.out.println(list2); // [hello]System.out.println(list3); // [hi, hello]System.out.println(list4); // [hi]

As we can see, none of the lists are modified in-place but a new copy is returned every time a modification is required.

如我们所见,列表中的任何内容都没有被修改,但是每次需要修改时都会返回一个新副本。

PCollections has a bunch of standard persistent data structures implemented for various use cases and is worth exploring. They also play nice with Java’s standard collection library which is quite handy.

PCollections具有针对各种用例实现的一堆标准持久性数据结构,值得探讨。 它们还可以很方便地与Java的标准集合库一起使用。

Kotlin comes with a standard library that already has immutable collections so if you’re using Kotlin, you’re good to go.

Kotlin附带了一个标准库,该库已经具有不可变的集合,因此,如果您使用的是Kotlin,那很好。

Persistent data structures are a vast subject and this section is only touching the tip of the iceberg. If you are interested in learning more about them, Chris Okasaki’s Purely Functional Data Structures comes highly recommended.

持久数据结构是一个广泛的主题,本节仅涉及冰山一角。 如果您有兴趣了解有关它们的更多信息,强烈建议克里斯·冈崎(Chris Okasaki)的Purely Functional Data Structures 。

(Summary)

Immutability and Purity are a potent combo allowing us to write safe, concurrent programs. In the next part, we’ll learn about higher order functions and closures.

不变性和纯度是有效的组合,使我们能够编写安全的并发程序。 在下一部分中,我们将学习高阶函数和闭包。

(Read Next)

Functional Programming for Android Developers — Part 3In the last post, we learned about immutability and concurrency. In this one, we’ll look at Higher Order Functions and…medium.com

Android开发人员的函数式编程—第3部分 在上一篇文章中,我们了解了不变性和并发性。 在这一部分中,我们将研究高阶函数和... medium.com

额外信用 (Extra credit)

I did a whole talk on immutability and concurrency at Droidcon India. Enjoy!

我在Droidcon India上讨论了不变性和并发性。 请享用!

If you liked this, click the ? below. I notice each one and I’m grateful for every one of them.

如果喜欢此,请单击“?”。 下面。 我注意到每个人,我感谢每个人。

For more musings about programming, follow me so you’ll get notified when I write new posts.

有关编程的更多信息,请关注我,以便在我撰写新文章时得到通知。

翻译自: https://www.freecodecamp.org/news/functional-programming-for-android-developers-part-2-5c0834669d1a/

android 函数式编程