为什么Scalaz实现Option的Monoid时会对f2函数进行两次求值?

7
scalaz的Option Monoid的定义如下:
implicit def optionMonoid[A: Semigroup]: Monoid[Option[A]] = new Monoid[Option[A]] {
  def append(f1: Option[A], f2: => Option[A]) = (f1, f2) match {
    case (Some(a1), Some(a2)) => Some(Semigroup[A].append(a1, a2))
    case (Some(a1), None)     => f1
    case (None, Some(a2))     => f2
    case (None, None)         => None
  }

  def zero: Option[A] = None
}
f2 是按名称传递的参数,这意味着每次调用都会计算表达式。在模式匹配中刚刚计算了该表达式,为什么要再次计算它呢?返回 Some(a2) 应该得到相同的结果,并且表达式 f2 可能非常昂贵。
我是否遗漏了什么?
参考链接:Scalaz's Option.scala source

3
可能是来自Haskell思维的传承,因为等价的定义是便宜的? - Mysterious Dan
你测试过这个吗?在里面加一些println并检查一下。 - Felix
是的,我测试了它,println被触发了两次。我会修复它并向scalaz提交一个pull请求。 - coltfred
1个回答

4

在我看来,这段代码的编写目的是为了突出问题的对称性和清晰度,而不是为了速度。你不能仅仅因为速度就放弃第二个参数的惰性,因为Semigroup就是这样定义的,在其他情况下,第二个参数的惰性可能是至关重要的。为了保持问题对称的视觉表现,你可能只需要添加:

val g2 = f2  // Force evaluation
(f1, g2) match { ...

或者类似的。

(如果按名称提供的参数可以称为lazy以自动记录它们的值,那将是很好的。)


1
我已将其作为拉取请求提交并被合并,但你的解决方案同样有效。https://github.com/scalaz/scalaz/pull/352 - coltfred

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