Scala中的应用函子与单子组合性能

3

我有两个单子实例 val a: M[A]val b: M[B]。以下代码存在性能差异吗?

def f: (A, B) => C

val applicativeCombine = (a |@| b)(f)

val monadCombine = 
  for {
   aa <- a
   bb <- b
  } yield f(aa, bb)

这是一个问题,还是它取决于情况?


你应该加上一个 scalaz 标签,因为从问题本身并不清楚 |@| 是从哪里来的。 - Travis Brown
1个回答

5
如果ab是返回futures并执行一些计算密集型操作的方法,那么applicativeCombine版本将在并行运行它们,而monadCombine则不会。这意味着|@|的速度可能接近两倍。
另一方面,如果ab是选项,甚至是没有进行计算密集型工作的futures,for版本可能更快,因为其展开形式涉及少数几个分配。
scala> import scalaz._, Scalaz._
import scalaz._
import Scalaz._

scala> import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe._

scala> showCode(reify((Option(1) |@| Option(2))(_ + _)).tree)
res0: String = Scalaz.ToApplyOps(Option.apply(1))(Scalaz.optionInstance).|@|(Option.apply(2)).apply(((x$1, x$2) => x$1.+(x$2)))(Scalaz.optionInstance)

scala> showCode(reify(for { a <- Option(1); b <- Option(2) } yield a + b).tree)
res1: String = Option.apply(1).flatMap(((a) => Option.apply(2).map(((b) => a.+(b)))))

但是在大多数程序中,这不太可能产生有意义的差异。

如果这是Cats,并且ab都是eithers,而您导入了“错误”的内容,以便获得FlatMapSyntaxEitherInstances而不是EitherSyntax,那么|@|版本可能会再次更快,因为for版本会导致至少几个额外的Unapply实例和其他一些东西。

所以简短的答案是:是的,这取决于情况,如果您真的关心性能,您需要为特定的用例进行仔细的基准测试。

然而,更喜欢应用程序版本的最好理由与性能无关——它更精确地表示您要执行的计算。您的ab彼此不依赖,但是for推导表达式却说他们之间存在依赖关系。请参见我在这里的回答,以获取更多讨论。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接