scala不可变和可变_Scala使期货变得轻松

scala不可变和可变

by Martin Budi

马丁·布迪(Martin Budi)

Scala使期货变得轻松 (Futures Made Easy with Scala)

Future is an abstraction to represent the completion of an asynchronous operation. Today it is commonly used in popular languages from Java to Dart. However, as modern applications are becoming more complex, composing them is also becoming more difficult. Scala utilizes a functional approach that makes it easy to visualize and construct Future composition.

Future是代表异步操作完成的抽象。 如今,它已广泛用于从Java到Dart的流行语言。 但是,随着现代应用程序变得越来越复杂,编写它们的难度也越来越大。 Scala利用一种功能性方法,可以轻松地可视化和构造Future构图。

This article aims to explain the basics in a pragmatic way. No jargon, no foreign terminology. You don’t even have to be a Scala programmer (yet). All you need to have is some understanding of a couple of higher-order functions: map and foreach. So let’s get started.

本文旨在以务实的方式解释基础知识。 没有行话,没有外来术语。 您甚至不必成为Scala程序员(尚未)。 您需要做的就是对一些高阶函数有一些了解:map和foreach。 因此,让我们开始吧。

In Scala, a future can be created as simple as this:

在Scala中,可以这样创建一个Future:

Future {"Hi"}

Now let’s run it and make a “Hi World”.

现在运行它并创建一个“ Hi World”。

Future {"Hi"} .foreach (z => println(z + " World"))

That’s all there is. We just ran a future using foreach, manipulated the result a bit, and printed it to the console.

这就是全部。 我们只是使用foreach运行了一个未来,对结果进行了一些处理,然后将其打印到控制台。

But how is it possible? So we normally associate foreach and map with collections: we unwrap the content and tinker with it. If you look at it, it’s conceptually similar to a future in the way we want to unwrap the output from Future{}and manipulate it. To have this happen the future needs to be completed first, hence “running” it. This is the reasoning behind the functional composition of Scala Future.

但是怎么可能呢? 因此,我们通常将foreach关联起来并与集合进行映射:我们将内容解开包装并进行修改。 如果您查看一下,它在概念上就类似于将来,我们希望从Future{}解开输出并对其进行操作。 为了使这种情况发生,未来需要先完成,然后“运行”。 这是Scala Future功能组成背后的原因。

In realistic applications, we want to coordinate not just one but several futures at once. A particular challenge is how to arrange them to run sequentially or simultaneously.

在实际应用中,我们不仅要协调一个未来,还要协调多个未来。 一个特殊的挑战是如何安排它们顺序同时运行。

顺序运行 (Sequential run)

When several futures start one after another like a relay race we call it sequential run. A typical solution would simply be placing a task in the previous task’s callback, a technique known as chaining. The concept is correct but it doesn’t look pretty.

当几个期货像接力赛一样接连开始时,我们称之为连续运行。 一个典型的解决方案就是将一个任务放在上一个任务的回调中,这是一种称为链接的技术。 这个概念是正确的,但看起来并不漂亮。

In Scala, we can use for-comprehension to help us abstract it. To see how it looks, let’s just go straight to an example.

在Scala中,我们可以使用理解来帮助我们对其进行抽象。 要查看它的外观,我们直接看一个例子。

import scala.concurrent.ExecutionContext.Implicits.global

object Main extends App {

  def job(n: Int) = Future {
    Thread.sleep(1000)
    println(n) // for demo only as this is side-effecting 
    n + 1
  }

  val f = for {
    f1 <- job(1)
    f2 <- job(f1)
    f3 <- job(f2)
    f4 <- job(f3)
    f5 <- job(f4)
  } yield List(f1, f2, f3, f4, f5)
  f.map(z => println(s"Done. ${z.size} jobs run"))
  Thread.sleep(6000) // needed to prevent main thread from quitting 
                     // too early 
}

The first thing to do is importing ExecutionContext whose role is to manage thread pool. Without it, our future will not run.

首先要做的是导入ExecutionContext,其作用是管理线程池。 没有它,我们的未来将无法运转。

Next, we define our “big job” which simply waits for a second and returns its input incremented by one.

接下来,我们定义“大工作”,仅等待一秒钟,然后将其输入增加一。

Then we have our for-comprehension block. In this structure, each line inside assigns a job’s result to a value with &lt;- which will then be available for any subsequent futures. We have arranged our jobs so that except for the first one, each one takes in the output of the previous job.

然后,我们获得了理解力。 在这种结构中,内部的每一行都将作业的结果分配为&l t;-的值,该值随后可用于任何后续期货。 我们已经安排好工作,以便除第一个工作外,每个工作都接上一个工作的输出。

Also, note that the result of a for-comprehension is also a future with output determined by yield. After the execution, the result will be available inside map. For our purpose, we simply put all the jobs’ outputs in a list and take its size.

另外,请注意,理解的结果也是未来,产量取决于产量。 执行后,结果将在map内可用。 出于我们的目的,我们仅将所有作业的输出放在列表中并确定其大小。

Let’s run it.

让我们运行它。

We can see the five futures fired one-by-one. It is important to note that this arrangement should only be used when the future is dependent on the previous future.

我们可以看到五种期货一一交易。 重要的是要注意,仅当将来取决于先前的将来时,才应使用此安排。

同时或并行运行 (Simultaneous or Parallel run)

If the futures are independent of each other then they should be fired simultaneously. For this purpose, we’re going to use Future.sequence. The name is a bit confusing, but in principle it simply takes a list of futures and transforms it into a future of list. The evaluation, however, is done asynchronously.

如果期货彼此独立,则应同时射击。 为此,我们将使用Future.sequence 。 名称有点混乱,但是原则上它只是获取期货列表并将其转换为列表的期货。 但是,评估是异步进行的。

Let’s create an example of mixed sequential and parallel futures.

让我们创建一个混合有序和并行期货的例子。

val f = for {
  f1 <- job(1)
  f2 <- Future.sequence(List(job(f1), job(f1)))
  f3 <- job(f2.head)
  f4 <- Future.sequence(List(job(f3), job(f3)))
  f5 <- job(f4.head)
} yield f2.size + f4.size
f.foreach(z => println(s"Done. $z jobs run in parallel"))

Future.sequence takes a list of futures that we wish to run simultaneously. So here we have f2 and f4 containing two parallel jobs. As the argument fed into Future.sequence is a list, the result is also a list. In a realistic application, the results may be combined for further computation. Here we’ll take the first element from each list with .head then pass it to f3 and f5 respectively.

Future.sequence列出了我们希望同时运行的期货列表。 所以在这里,我们有f2和f4包含两个并行作业。 由于传入Future.sequence的参数是一个列表,所以结果也是一个列表。 在实际应用中,可以将结果组合起来以进行进一步的计算。 在这里,我们将使用.head从每个列表中获取第一个元素,然后分别将其传递给f3和f5。

Let’s see it in action:

让我们来看看它的作用:

We can see the jobs in 2 and 4 fired simultaneously indicating successful parallelism. It is worth noting that parallel execution is not always guaranteed since it depends on available threads. If there are not enough threads then only some of the jobs will run in parallel. The others, however, will wait until some more threads are freed.

我们可以看到同时触发了2和4的作业,这表明并行处理成功。 值得注意的是,并行执行并非总是保证的,因为它取决于可用线程。 如果没有足够的线程,则只有部分作业将并行运行。 但是,其他线程将等待,直到释放更多线程。

从错误中恢复 (Recovering from errors)

Scala Future incorporates recover that acts as a back-up future when an error occurs. This allows the future composition to finish even with failures. To illustrate, consider this code:

Scala Future合并了恢复 ,当发生错误时,该恢复充当备份的未来 这使得将来的合成即使失败也可以完成。 为了说明,请考虑以下代码:

Future {"abc".toInt}
.map(z => z + 1)

Of course, this will not work, as “abc” is not an int. With recover, we can salvage it by passing a default value. Let’s try passing a zero:

当然,这将不起作用,因为“ abc”不是整数。 使用recover,我们可以通过传递默认值来挽救它。 让我们尝试传递零:

Future {"abc".toInt}
.recover {case e => 0}
.map(z => z + 1)

Now the code will run and produce one as a result. In composition, we can fine-tune each future like this to make sure the process won’t fail.

现在,代码将运行并生成一个结果。 在合成方面,我们可以像这样微调每个未来,以确保过程不会失败。

However, there are also times when we want to reject errors explicitly. For this purpose, we can use Future.succesful and Future.failed to signal validation result. And if we don’t care about individual failure we can position recover to catch any error inside the composition.

但是,有时我们也希望明确拒绝错误。 为此,我们可以使用Future.succesful和Future.failed来发送验证结果。 而且,如果我们不关心单个故障,则可以定位恢复以捕获组合中的任何错误。

Let’s work another bit of code using for-comprehension that checks if the input is a valid int and lower than 100. Future.failed and Future.successful are both futures so we don’t need to wrap it in one. Future.failed in particular requires a Throwable so we’re going to create a custom one for input larger than 100. After putting it all up together we would have as follows:

让我们使用for-comprehension编写另一段代码,该代码检查输入是否为有效的int且小于100。Future.failed和Future.successful均为Future,因此我们无需将其包装在一起。 特别是Future.failed需要Throwable,因此我们将为输入大于100的对象创建一个自定义对象。将所有内容放在一起后,我们将具有以下内容:

val input = "5" // let's try "5", "200", and "abc"
case class NumberTooLarge() extends Throwable()
val f = for {
   f1 <- Future{ input.toInt }
   f2 <- if (f1 > 100) {
            Future.failed(NumberTooLarge())
          } else {
            Future.successful(f1)
          }
} yield f2
f map(println) recover {case e => e.printStackTrace()}

Notice the positioning of recover. With this configuration, it will simply intercept any error occurring inside the block. Let’s test it with several different inputs “5”, “200”, and “abc”:

注意恢复的位置。 使用此配置,它将仅拦截块内发生的任何错误。 让我们用几个不同的输入“ 5”,“ 200”和“ abc”对其进行测试:

"5"   -> 5
"200" -> NumberTooLarge stacktrace
"abc" -> NumberFormatException stacktrace

“5” reached the end no problem. “200” and “abc” arrived in recover. Now, what if we want to handle each error separately? This is where pattern matching comes into play. Expanding the recover block, we can have something like this:

“ 5”到达终点没问题。 “ 200”和“ abc”到达恢复状态。 现在,如果我们要分别处理每个错误怎么办? 这就是模式匹配起作用的地方。 扩展recover块,我们可以得到以下内容:

case e => 
  e match {
    case t: NumberTooLarge => // deal with number > 100
    case t: NumberFormatException => // deal with not a number
    case _ => // deal with any other errors
  }
}

You might probably have guessed it but an all-or-nothing scenario like this is commonly used in public APIs. Such service wouldn’t process invalid input but needs to return a message to inform the client what they did wrong. By separating exceptions, we can pass a custom message for each error. If you like to build such service (with a very fast web framework), head over to my Vert.x article.

您可能已经猜到了,但是在公共API中通常使用这种全有或全无的方案。 此类服务不会处理无效的输入,但需要返回一条消息以告知客户端他们做错了什么。 通过分离异常,我们可以为每个错误传递自定义消息。 如果您想构建这样的服务(使用非常快速的Web框架),请转至我的Vert.x文章

Scala以外的世界 (The world outside Scala)

We have talked a lot about how easy Scala Future is. But is it really? To answer it we need to look at how it’s done in other languages. Arguably the closest language to Scala is Java as both operate on JVM. Furthermore, Java 8 has introduced Concurrency API with CompletableFuture which is also able to chain futures. Let’s rework the first sequence example with it.

我们已经谈论了很多有关Scala Future的事情。 但这是真的吗? 要回答这个问题,我们需要看看它是如何用其他语言完成的。 可以说,最接近Scala的语言是Java,因为两者都在JVM上运行。 此外,Java 8引入了带有CompletableFuture的并发API,该API也能够链接期货。 让我们用它重做第一个序列示例。

That’s sure a lot of stuff. And to code this I had to look up supplyAsync and thenApply among so many methods in the documentation. And even if I know all these methods, they can only be used within the context of the API.

那肯定有很多东西。 为了对此进行编码,我必须在文档中的众多方法中查找supplyAsync,然后应用 。 即使我知道所有这些方法,也只能在API上下文中使用它们。

On the other hand, Scala Future is not based on API or external libraries but a functional programming concept that is also used in other aspects of Scala. So with an initial investment in covering the fundamentals, you can reap the reward of less overhead and higher flexibility.

另一方面,Scala Future不是基于API或外部库,而是一个功能性编程概念,该概念也已在Scala的其他方面使用。 因此,通过对基础知识进行初步投资,您可以获得较低的开销和更高的灵活性的回报。

结语 (Wrapping up)

That’s all for the basics. There’s more to Scala Future but what we have here has covered enough ground to build real-life applications. If you like to read more about Future or Scala, in general, I’d recommend Alvin Alexander tutorials, AllAboutScala, and Sujit Kamthe’s article that offers easy to grasp explanations.

这就是基本知识。 Scala Future还有更多功能,但是我们这里已经涵盖了足够的基础来构建实际应用程序。 如果您想阅读有关Future或Scala的更多信息,通常,我建议Alvin Alexander教程AllAboutScalaSujit Kamthe的文章 ,这些文章提供了易于理解的解释。

翻译自: https://www.freecodecamp.org/news/futures-made-easy-with-scala-da1beb3bb281/

scala不可变和可变

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值