【问题标题】:Scala cats, flatMap on cartesian?Scala猫,笛卡尔上的flatMap?
【发布时间】:2018-02-20 19:14:10
【问题描述】:

给定:

import cats.syntax.cartesian._

type M[X] = Future[Either[Error, X]]
val ma: M[A] = ???
val mb: M[B] = ???

我知道我可以做到:

def doStuff(a: A, b: B): C = ???
val result: M[C] = (ma |@| mb).map(doStuff)

但是我怎么做平面地图呢? CartesianBuilders 中没有 flatMap

def doFancyStuff(a: A, b: B): M[C] = ???
val result: M[C] = (ma |@| mb).flatMap(doFancyStuff)

【问题讨论】:

  • 那是因为CartesianBuilderSemigroup[A],而不是Monad[A]
  • 小备注:cats.syntax.cartesian 的 Scaladoc 有一个弃用警告:“已弃用(自版本 1.0.0-RC1 起)使用 cat.syntax.semigroupal 代替”是有意的吗?
  • @Andrey 他们将|@| 切换到mapN

标签: scala scala-cats cartesian


【解决方案1】:

我认为主要问题是在Future[Either[Error, X]] 上的flatMapEitherT[Future, Error, X]-monad 堆栈的意义上很尴尬,因为Future 的原始flatMap 妨碍了,并且编译器不是在寻找可以同时处理FutureEither 组合的monad 实例。但是,如果您将期货包装在 EitherT 中,一切都会顺利进行。

适用于猫 1.0.1

在下文中,(a,b).tupled 对应于Cartesian.product(a, b)(a, b).mapN 对应于已弃用的(a |@| b).map

给定类型ABCError,以下代码sn-ps在cats 1.0.1下编译:

如果您想保留M 的定义(您可能应该),那么您 可以通过将所有内容包装在EitherT 中来避免上述问题,然后 提取value:

import scala.util.Either
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global // required by Monad[Future]
import cats.instances.future._                            // Monad for `Future`
import cats.syntax.apply._                                // `tupled` and `mapN`
import cats.data.EitherT                                  // EitherT monad transformer

type M[X] = Future[Either[Error, X]]
val ma: M[A] = ???
val mb: M[B] = ???

def doStuff(a: A, b: B): C = ???
val result1: M[C] = (EitherT(ma), EitherT(mb)).mapN(doStuff).value

def doFancyStuff(a: A, b: B): M[C] = ???
val result2: M[C] = (for {
  ab <- (EitherT(ma), EitherT(mb)).tupled
  c <- EitherT(doFancyStuff(ab._1, ab._2))
} yield c).value

不过,如果这看起来太别扭了,你可以调整M的定义, 以下变体可能会稍微短一些:

import scala.util.Either
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global // required by Monad[Future]
import cats.instances.future._                            // Monad for `Future`
import cats.syntax.apply._                                // `tupled` and `mapN`
import cats.data.EitherT                                  // EitherT monad transformer

type M[X] = EitherT[Future, Error, X]
val ma: M[A] = ??? // either adjust signatures, or wrap result in EitherT(res)
val mb: M[B] = ???

def doStuff(a: A, b: B): C = ???
val result1: M[C] = (ma, mb).mapN(doStuff)

def doFancyStuff(a: A, b: B): M[C] = ???
val result2: M[C] = (ma, mb).tupled.flatMap{
  case (a, b) => doFancyStuff(a, b)
}

这是因为(ma, mb).tupled 构建了一个M[(A, B)],其中M 是前面提到的monad 堆栈,然后可以通过(A, B) =&gt; M[C] 函数轻松地将flatMapped 转换为M[C]

对于带有Cartesian(未经测试)的旧版本

假设(ma, mb).tupled对应deprecated的Cartesian.product(ma, mb).mapN对应deprecated的(ma |@| mb).map,则上述代码中result1result2的两个定义为1.0.1 translate到:

val result1: M[C] = (ma |@| mb).map(doStuff)
val result2: M[C] = Cartesian[M].product(ma, mb).flatMap{ 
  case (a, b) => doFancyStuff(a, b) 
}

同样,这只是因为Cartesian[M].product(ma, mb)M[A]M[B] 构建M[(A, B)],其中M[X] 定义为EitherT[Future, Error, X]。如果它被定义为Future[Either[Error, X]],那么flatMap 将在Future 上被调用,而不是doFancyStuff,我们将不得不传递Either[Error, (A, B)] =&gt; Future[Either[Error, C]] 之类的东西,这可能不是你想要的。

【讨论】:

  • 你的第二个 sn-p 为我工作。我有 EitherT,但在问题中错过了它。幸运的是我可以升级我的猫,所以tupled.flatMap 就是我想要的。
  • @Vituel 啊,好吧。然后你可以从三个中选择最短的版本,很好。 :) 关于第三个 sn-p 是否会编译的任何 cmets?我真的没有为此费心用旧的猫版本创建一个单独的项目。
  • 是的,你的第三个片段在猫 0.9.0 中编译得很好。
  • @Vituel 啊,好的,感谢您的检查。另一件Traverse-事情最后怎么样了? Traverse[Seq] 的实现确实在 1.0.1 中编译。是不是因为某种原因无法使用?
猜你喜欢
  • 2022-12-09
  • 2017-02-08
  • 2017-06-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-16
  • 1970-01-01
相关资源
最近更新 更多