在Scalaz中,Future单子的左恒等律遭到了破坏。

8

假设我为Future定义了一个Monad类型类的实例:

val futureMonad = new Monad[Future] {
  override def point[A](a: ⇒ A): Future[A] =
    Future(a)

  override def bind[A, B](fa: Future[A])(f: A => Future[B]): Future[B] =
    fa flatMap f
}

严格来说,这不是一个单子(Monad),因为它违反了左恒等律:

futureMonad.point(a) bind f == f(a)

如果f抛出异常,左侧表达式的结果将是一个失败的Future,而右侧当然会抛出异常。
但这种违规行为有什么实际影响?系统可能会因此失败的方式有哪些?

1
如果"f"的类型为A => Future[B],那么直接应用不应该导致失败的Future[B]吗?反之,如果f仅在bind方法内抛出异常,那么该异常不会从monad中“逃脱”吗? - Jonny Coombes
任何函数理论上都可能抛出异常(由于堆耗尽或其他原因),因此根据您的问题的前提...难道不是每个单子都违反了相等法则吗?您是否将行为相等的定义与类型/结构相等混淆了? - Jonny Coombes
@badtrumpet 即使 f 的类型为 A => Future[B],它仍然可能抛出异常。而且,不会:如果 f 在绑定方法中抛出异常,它不会逃脱。flatMap 会处理这种情况。 - Otavio Macedo
@badtrumpet 不,因为大多数Monad并不尝试捕获异常,所以它们会在方程的两侧都抛出异常,因此这两个方程仍将“行为上相等”。 - Robin Green
酷酷的人们 - 我认为从下面的帖子来看,我想要表达的是,如果一个单子通过在 flatMap 中捕获异常并防止它们逃逸来遵守相等法则(这并不是真正的纯粹 - 因为异常是一种副作用),那么函数 f 也应该遵守同样的原则(防弹),否则谈论相等就有点毫无意义了... - Jonny Coombes
2个回答

6

TryFuture 这样的单子会用更有用的法则来交换一个单子法则,这个法则在它们应该被使用的上下文中更加实用: 由 (TryFuture)、flatMap、map 组成的表达式永远不会抛出非致命异常。将其称为“防弹”原则。 因此,实际上这种方法确实可以保护你免受许多故障的影响,而左单元法则是故意失败的。


4

就 for comprehensions(for循环)而言,这句话的意思是,下面的重构不保留语义:

for (fut <- Future(a); x <- f(fut)) yield x  ==>  f(a)

实际上,这只是另一种写左单位律的方式。

进一步解释那个无效的重构:

for (fut <- Future(a); x <- f(fut)) yield x
==>  for (x <- f(a)) yield x  // by left identity law: WRONG, because left identity law does not hold
==>  f(a)                     // by 1st functor law:   WRONG, because previous line was wrong

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