简体   繁体   English

猫效应:如何将 Map[x,IO[y]] 转换为 IO[Map[x,y]]

[英]cats-effect:How to transform Map[x,IO[y]] to IO[Map[x,y]]

I have a map of string to IO like this Map[String, IO[String]] , I want to transform it into IO[Map[String, String]] .我有一个字符串到 IO 的映射,就像这样Map[String, IO[String]] ,我想把它转换成IO[Map[String, String]] How to do it?怎么做?

You'll have to be a little careful with this one.你必须小心一点。 Maps in Scala are unordered, so if you try to use cats's sequence like this… Scala 中的映射是无序的,所以如果你尝试像这样使用猫的sequence ......

import cats.instances.map._
import cats.effect.IO
import cats.UnorderedTraverse

object Example1 {
    type StringMap[V] = Map[String, V]
    val m: StringMap[IO[String]] = Map("1" -> IO{println("1"); "1"})
    val n: IO[StringMap[String]] = UnorderedTraverse[StringMap].unorderedSequence[IO, String](m)
}

you'll get the following error:你会得到以下错误:

Error: could not find implicit value for evidence parameter of type cats.CommutativeApplicative[cats.effect.IO]

The issue here is that the IO monad is not actually commutative.这里的问题是 IO monad 实际上不是可交换的。 Here is the definition of commutativity :这是交换性的定义

map2(u, v)(f) = map2(v, u)(flip(f)) // Commutativity (Scala)

This definition shows that the result is the same even when the effects happen in a different order.这个定义表明即使效果以不同的顺序发生,结果也是相同的。

You can make the above code compile by providing an instance of CommutativeApplicative[IO] but that still doesn't make the IO monad commutative.您可以通过提供CommutativeApplicative[IO]的实例来编译上述代码,但这仍然不会使 IO monad 可交换。 If you run the following code you can see the side effects are not processed in the same order:如果运行以下代码,您可以看到副作用未按相同顺序处理:

import cats.effect.IO
import cats.CommutativeApplicative

object Example2 {
  implicit object FakeEvidence extends CommutativeApplicative[IO] {
    override def pure[A](x: A): IO[A] = IO(x)
    override def ap[A, B](ff: IO[A => B])(fa: IO[A]): IO[B] =
      implicitly[Applicative[IO]].ap(ff)(fa)
  }

  def main(args: Array[String]): Unit = {
    def flip[A, B, C](f: (A, B) => C) = (b: B, a: A) => f(a, b)
    val fa = IO{println(1); 1}
    val fb = IO{println(true); true}
    val f  = (a: Int, b: Boolean) => s"$a$b"
    println(s"IO is not commutative: ${FakeEvidence.map2(fa, fb)(f).unsafeRunSync()} == ${FakeEvidence.map2(fb, fa)(flip(f)).unsafeRunSync()} (look at the side effects above^^)")
  }
}

Which outputs the following:输出以下内容:

1
true
true
1
IO is not commutative: 1true == 1true (look at the side effects above^^)

In order to get around this I would suggest making your map something with an order, like a List, where sequence will not require commutativity.为了解决这个问题,我建议您将地图制作成具有顺序的东西,例如列表,其中序列不需要交换性。 The following example is just one way to do this:以下示例只是执行此操作的一种方法:

import cats.effect.IO
import cats.implicits._

object Example3 {
  val m: Map[String, IO[String]] = Map("1" -> IO {println("1"); "1"})
  val l: IO[List[(String, String)]] = m.toList.traverse[IO, (String, String)] { case (s, io) => io.map(s2 => (s, s2))}
  val n: IO[Map[String, String]] = l.map { _.toMap }
}

It would be nice to use unorderedTraverse here, but as codenoodle pointed out, it doesn't work because IO is not a commutative applicative.在这里使用unorderedTraverse会很好,但正如 codenoodle 指出的那样,它不起作用,因为IO不是可交换的应用程序。 However there is a type that is, and it's called IO.Par .但是有一种类型叫做IO.Par Like the name suggests, its ap combinator won't execute things sequentially but in parallel, so it's commutative – doing a and then b is not the same as doing b and then a, but doing a and b concurrently is the same as doing b and a concurrently.顾名思义,它的ap组合器不会顺序执行而是并行执行,所以它是可交换的——执行 a 然后执行 b 与执行 b 然后执行 a 不同,但同时执行 a 和 b 与执行 b 相同和一个同时。

So you can use unorderedTraverse using a function that doesn't return IO but IO.Par .因此,您可以使用不返回IO但返回IO.Par的函数来使用unorderedTraverse However the downside to that is that now you need to convert from IO to IO.Par and then back – hardly an improvement.然而,这样做的缺点是现在您需要从IO转换为IO.Par然后再转换回来——几乎没有什么改进。

To solve this problem, I have added the parUnorderedTraverse method in cats 2.0 that will take care of these conversions for you.为了解决这个问题,我在 cats 2.0 中添加了parUnorderedTraverse方法,它将为您处理这些转换。 And because it all happens in parallel it will also be more efficient!而且因为这一切都是并行发生的,所以它也会更有效率! There are also parUnorderedSequence , parUnorderedFlatTraverse and parUnorderedFlatSequence .还有parUnorderedSequenceparUnorderedFlatTraverseparUnorderedFlatSequence

I should also point out that this works not only for IO but also for everything else with a Parallel instance, such as Either[A, ?] (where A is a CommutativeSemigroup ).我还应该指出,这不仅适用于IO ,还适用于具有Parallel实例的所有其他内容,例如Either[A, ?] (其中ACommutativeSemigroup )。 It should also be possible for List / ZipList , but nobody appears to have bothered to do it yet. List / ZipList也应该是可能的,但似乎还没有人费心去做。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM