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

时间:2018-12-27 13:36:56

标签: scala io scala-cats io-monad

我有一个类似Map[String, IO[String]]的字符串到IO的映射,我想将其转换为IO[Map[String, String]]。怎么做?

2 个答案:

答案 0 :(得分:8)

您必须对此稍加小心。 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)
}

您将收到以下错误:

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

这里的问题是IO monad实际上不是可交换的。这是definition of commutativity

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

此定义表明,即使效果以不同顺序发生,结果也是相同的。

您可以通过提供CommutativeApplicative[IO]的实例来编译上面的代码,但这仍然不能使IO monad可交换。如果运行以下代码,则可以看到副作用未按相同顺序处理:

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^^)")
  }
}

输出以下内容:

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

为了解决这个问题,我建议您按照一定的顺序来制作地图,例如列表,其中的序列不需要可交换性。以下示例只是执行此操作的一种方法:

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 }
}

答案 1 :(得分:2)

在这里使用unorderedTraverse会很好,但是正如codenoodle指出的那样,它不起作用,因为IO不是可交换的应用程序。但是,有一个类型称为IO.Par。顾名思义,它的ap组合器不会顺序执行而是并行执行,因此它是可交换的-先执行a然后b的操作与先执行b然后a的操作不同,但是并行执行a和b < em>与并行执行b和a相同。

因此,您可以使用unorderedTraverse而不使用返回IO的函数。但是,这样做的缺点是现在您需要从IO.Par转换为IO,然后再转换–几乎没有改善。

为解决此问题,我在cats 2.0中添加了IO.Par方法,该方法将为您完成这些转换。而且由于这一切都是并行发生的,因此也将更加有效!还有parUnorderedTraverseparUnorderedSequenceparUnorderedFlatTraverse

我还应该指出,这不仅适用于parUnorderedFlatSequence,还适用于具有IO实例的其他所有内容,例如Parallel(其中Either[A, ?]是{ {1}})。 A / CommutativeSemigroup也应该有可能,但是似乎没人愿意这样做。