cats effect仅评估`for`推导的最终部分,忽略其余部分。

huangapple go评论52阅读模式
英文:

cats effect evaluates only the final for coprehension and ignores rest

问题

以下是您提供的代码的翻译:

我是一个函数式编程和cats effect的新手
开始练习cats effect 3的代码如下

```scala
package com.scalaFPLove.FabioLabellaTalks

import cats.effect.IOApp
import cats.effect.{IO, Concurrent}
import cats.effect.kernel.Deferred
import cats.implicits._
import cats.kernel
import cats.effect.kernel.Ref
import cats.effect.unsafe.IORuntime.global

object RefAndDeferredConcurrency extends IOApp.Simple {

  override def run: IO[Unit] = {
    for {
      _ <- IO.println("Hello1")
      _ <- IO.println("World1")
    } yield ()
    for {
      _ <- IO.println("Hello2")
      _ <- IO.println("World2")
    } yield ()
  }
}

我期望的输出是

Hello1
World1
Hello2
World2

但实际输出是

> Hello2
> World2

我无法理解背后的思想,我尝试了许多不同的方法,只有最后的for推导被评估,忽略了其他的。请帮助我理解这一点。


<details>
<summary>英文:</summary>

I&#39;m a newbie to functional programming and cats effect.
Started practicing cats effect 3 with the below code


package com.scalaFPLove.FabioLabellaTalks

import cats.effect.IOApp
import cats.effect.{IO, Concurrent}
import cats.effect.kernel.Deferred
import cats.implicits._
import cats.kernel
import cats.effect.kernel.Ref
import cats.effect.unsafe.IORuntime.global

object RefAndDeferredConcurrency extends IOApp.Simple {

override def run: IO[Unit] = {
for {
_ <- IO.println("Hello1")
_ <- IO.println("World1")
} yield ()
for {
_ <- IO.println("Hello2")
_ <- IO.println("World2")
} yield ()
}
}


The output I expected is

Hello1
world1
Hello2
world2

but the actual output is 

&gt; Hello2
&gt; world2

Unable to understand the idea behind this, I tried many different things and only the last for comprehension is evaluated ignoring the rest. please help me understand this.




</details>


# 答案1
**得分**: 3

以下是翻译好的内容:

最简单的解释是这样的:`IO[A]` 就像 `() =&gt; A`(或 `() =&gt; Future[A]`),除了它还有用于 for-comprehension 的 `map` 和 `flatMap`。

当你有:

```scala
override def run: IO[Unit] = {
  for {
    _ &lt;- IO.println(&quot;Hello1&quot;)
    _ &lt;- IO.println(&quot;World1&quot;)
  } yield ()
  for {
    _ &lt;- IO.println(&quot;Hello2&quot;)
    _ &lt;- IO.println(&quot;World2&quot;)
  } yield ()
}

它基本上与以下非常相似:

def IOprintln(str: String): () =&gt; Unit = _ =&gt; println(str)

def run: () =&gt; Unit = {
  () =&gt; {
    val _ = IOprintln(&quot;Hello1&quot;)()
    val _ = IOprintln(&quot;World1&quot;)()
    ()
  }
  () =&gt; {
    val _ = IOprintln(&quot;Hello2&quot;)()
    val _ = IOprintln(&quot;World2&quot;)()
    ()
  }
}

(在你的程序中,runIOApp.Simplemain 中被调用)。

你看到发生了什么吗?我们为程序创建了两个 recipes,但从未将它们组合在一起,代码块的值是最后一个表达式。

这些带有 recipes 的表达式 - 由于 () =&gt; ... 的本质 - 直到运行它们 都不会计算副作用。这非常有用,因为它允许你做像这样的事情:

def program(condition: Boolean) = {
  val a = IO.println(&quot;hello world 1&quot;)
  val b = IO.println(&quot;hello world 2&quot;)
  if (condition) a else b
}

在这里,我们的程序将只打印一条消息,具体取决于条件,而不会在甚至检查需要哪个之前同时打印两个消息。

基本上,将执行的是(在某个地方,在某个时刻)最后的 IO 表达式。如果你使用 flatMap 构建它,那么只有 flatMap 中的最后一个表达式会成为配方的一部分,依此类推。例如,你的原始程序可以展开为:

override def run: IO[Unit] = {
  IO.println(&quot;Hello1&quot;).flatMap { _ =&gt;
    IO.println(&quot;World1&quot;).map { _ =&gt;
      ()
    }
  }
  IO.println(&quot;Hello2&quot;).flatMap { _ =&gt;
    IO.println(&quot;World2&quot;).map { _ =&gt;
      ()
    }
  }
}

当你记住:

  • IO 是某处某人评估的值(就像函数一样)
  • 在评估数据时,只能评估成为该数据一部分的内容
  • 代码块的值是它的最后一个表达式的值

应该清楚什么将成为最终程序的一部分,什么不会成为最终程序的一部分。

英文:

The simplest explanation is this: IO[A] is like () =&gt; A (or () =&gt; Future[A]). Except it comes with map and flatMap used in for-comprehension.

When you have:

override def run: IO[Unit] = {
  for {
    _ &lt;- IO.println(&quot;Hello1&quot;)
    _ &lt;- IO.println(&quot;World1&quot;)
  } yield ()
  for {
    _ &lt;- IO.println(&quot;Hello2&quot;)
    _ &lt;- IO.println(&quot;World2&quot;)
  } yield ()
}

it's basically very similar to:

def IOprintln(str: String): () =&gt; Unit = _ =&gt; println(str)

def run: () =&gt; Unit = {
  () =&gt; {
    val _ = IOprintln(&quot;Hello1&quot;)()
    val _ = IOprintln(&quot;World1&quot;)()
    ()
  }
  () =&gt; {
    val _ = IOprintln(&quot;Hello2&quot;)()
    val _ = IOprintln(&quot;World2&quot;)()
    ()
  }
}

(In your program run is called in IOApp.Simple's main).

You see what happened here? We created 2 recipes for a program, but never combined them together, and the value of block of code is the last expression.

And these expressions with recepies - by the very nature of () =&gt; ... - are not computing side effects until you run them. It' very useful because it allow you to do things like:

def program(condition: Boolean) = {
  val a = IO.println(&quot;hello world 1&quot;)
  val b = IO.println(&quot;hello world 2&quot;)
  if (condition) a else b
}

where our program would print only 1 thing, depending on the condition, but not 2 of them at once before even checking which is needed.

Basically what would be executed (somewhere, at some point) is the last IO expression. If you build it using flatMap then only the last expression in that flatMap would become part of the recipe, and so on. E.g. your original program can be desugared to:

override def run: IO[Unit] = {
  IO.println(&quot;Hello1&quot;).flatMap { _ =&gt;
    IO.println(&quot;World1&quot;).map { _ =&gt;
      ()
    }
  }
  IO.println(&quot;Hello2&quot;).flatMap { _ =&gt;
    IO.println(&quot;World2&quot;).map { _ =&gt;
      ()
    }
  }
}

and when you remember that

  • IO is a value that someone somewhere evaluates (like a function)
  • when evaluating data you can only evaluate what's become a part of that data
  • the value of a block is the value of it's last expression

it should be clear what will and what will not become part of a final program.

huangapple
  • 本文由 发表于 2023年5月30日 07:14:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/76360772.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定