Scalacheck Try:Monadic Associativity law 通过生成的函数
Scalacheck Try: Monadic Associativity law passes with generated functions
当我 运行 以下 属性 它通过:
import org.scalacheck.Prop.forAll
import scala.util.Try
forAll { (m: Try[String], f: String => Try[Int], g: Int => Try[Double]) =>
m.flatMap(f).flatMap(g) == m.flatMap(x => f(x).flatMap(g))
}.check // + OK, passed 100 tests.
但是它应该会失败,因为 Try
不是单子的(同样适用于 "left identity")
当未生成函数时,它按预期工作并且 属性 失败:
val f2 = (s: String) => Try { s.toInt }
val g2 = (i: Int) => Try { i / 2d }
forAll { (m: Try[String]) =>
m.flatMap(f2).flatMap(g2) == m.flatMap(x => f2(x).flatMap(g2))
}.check // ! Falsified after 0 passed tests.
为什么它不适用于生成的函数?
首先,单子结合律是应该在这里通过。 Try
只违反了左恒等式:
unit(x).flatMap(f) == f(x)
因为 Try
永远不会抛出其中发生的异常(这是设计使然;为了更安全而自愿交换身份)。因此,如果 f
抛出异常,左侧将是一个失败的 Try,而右侧将简单地爆炸。
但是结合律:
m.flatMap(f).flatMap(g) == m.flatMap(x ⇒ f(x).flatMap(g))
应该持有。双方每次都应该要么成功要么失败,但不可能炸毁一侧而不炸毁另一侧,因为 f
和 g
都在两侧的 flatMap 中。
这里发生的是您的第二个代码段,即您自己定义函数的代码段,抛出了不同类型的异常。如果你简单地打印出发生了什么:
...
{
val fst = m.flatMap(f).flatMap(g)
val snd = m.flatMap(x => f(x).flatMap(g))
println(fst)
println(snd)
fst == snd
}
}.check
...
你可以自己看看。这是第一种情况,具有未定义的函数:
// ...
// Failure(java.lang.Error)
// Failure(java.lang.Error)
// Success(5.16373771232299E267)
// Success(5.16373771232299E267)
// Failure(java.lang.Exception)
// Failure(java.lang.Exception)
// Failure(java.lang.Error)
// Failure(java.lang.Error)
// ...etc...
现在是第二种情况,具有定义的功能:
// ...
// Failure(java.lang.Error)
// Failure(java.lang.Error)
// Failure(java.lang.Exception)
// Failure(java.lang.Exception)
// Failure(java.lang.NumberFormatException: For input string: "걡圤")
// Failure(java.lang.NumberFormatException: For input string: "걡圤")
就是这样。第二个在某个时候为您的 f2
提供了一个奇怪的 Chinese-character-filled 字符串;这种情况在第一种情况下永远不会发生。这与 Scalacheck 生成测试用例的方式有关。给定一个函数 String => Try[Int]
,它要么给出一个有效整数,要么构成一些通用异常以创建一个失败案例。它不会使用在 String 上定义的具体函数(例如您使用的 toInt
)。
因此,第二种情况会导致数字格式异常。为什么第一种情况下的异常每次比较都会产生true
,而第二种情况下的数字格式异常在比较时会产生false
?我会把这个留给 Java 专家。我认为这与 ==
依赖于 Java 的 equals
这一事实有关,它比较引用,但 null == null
产生 true,所以我猜在某些时候内部比较异常字段,第一种情况在整个地方产生空值(请记住,这些异常是通用的,因为它们是由 Scalacheck 组成的),第二种情况具有实际异常(更具体地说,java.lang.NumberFormatException
),其中包含实际对象它们,这导致看似相同的异常在相等比较时产生错误。尝试将测试条件从 fst == snd
更改为 fst.toString == snd.toString
,您会发现两种情况都会通过。
很抱歉没有提供 100% 完整的答案,但我需要投入大量时间来调试愚蠢的 Java 对象和引用的处理以及如何在各个级别实现平等 类 的例外,更不用说整个 "null == null is true" 哲学困境了。如果您(像我一样)对 Java 怪癖没那么感兴趣,那么我想这可以回答您的问题。
当我 运行 以下 属性 它通过:
import org.scalacheck.Prop.forAll
import scala.util.Try
forAll { (m: Try[String], f: String => Try[Int], g: Int => Try[Double]) =>
m.flatMap(f).flatMap(g) == m.flatMap(x => f(x).flatMap(g))
}.check // + OK, passed 100 tests.
但是它应该会失败,因为 Try
不是单子的(同样适用于 "left identity")
当未生成函数时,它按预期工作并且 属性 失败:
val f2 = (s: String) => Try { s.toInt }
val g2 = (i: Int) => Try { i / 2d }
forAll { (m: Try[String]) =>
m.flatMap(f2).flatMap(g2) == m.flatMap(x => f2(x).flatMap(g2))
}.check // ! Falsified after 0 passed tests.
为什么它不适用于生成的函数?
首先,单子结合律是应该在这里通过。 Try
只违反了左恒等式:
unit(x).flatMap(f) == f(x)
因为 Try
永远不会抛出其中发生的异常(这是设计使然;为了更安全而自愿交换身份)。因此,如果 f
抛出异常,左侧将是一个失败的 Try,而右侧将简单地爆炸。
但是结合律:
m.flatMap(f).flatMap(g) == m.flatMap(x ⇒ f(x).flatMap(g))
应该持有。双方每次都应该要么成功要么失败,但不可能炸毁一侧而不炸毁另一侧,因为 f
和 g
都在两侧的 flatMap 中。
这里发生的是您的第二个代码段,即您自己定义函数的代码段,抛出了不同类型的异常。如果你简单地打印出发生了什么:
...
{
val fst = m.flatMap(f).flatMap(g)
val snd = m.flatMap(x => f(x).flatMap(g))
println(fst)
println(snd)
fst == snd
}
}.check
...
你可以自己看看。这是第一种情况,具有未定义的函数:
// ...
// Failure(java.lang.Error)
// Failure(java.lang.Error)
// Success(5.16373771232299E267)
// Success(5.16373771232299E267)
// Failure(java.lang.Exception)
// Failure(java.lang.Exception)
// Failure(java.lang.Error)
// Failure(java.lang.Error)
// ...etc...
现在是第二种情况,具有定义的功能:
// ...
// Failure(java.lang.Error)
// Failure(java.lang.Error)
// Failure(java.lang.Exception)
// Failure(java.lang.Exception)
// Failure(java.lang.NumberFormatException: For input string: "걡圤")
// Failure(java.lang.NumberFormatException: For input string: "걡圤")
就是这样。第二个在某个时候为您的 f2
提供了一个奇怪的 Chinese-character-filled 字符串;这种情况在第一种情况下永远不会发生。这与 Scalacheck 生成测试用例的方式有关。给定一个函数 String => Try[Int]
,它要么给出一个有效整数,要么构成一些通用异常以创建一个失败案例。它不会使用在 String 上定义的具体函数(例如您使用的 toInt
)。
因此,第二种情况会导致数字格式异常。为什么第一种情况下的异常每次比较都会产生true
,而第二种情况下的数字格式异常在比较时会产生false
?我会把这个留给 Java 专家。我认为这与 ==
依赖于 Java 的 equals
这一事实有关,它比较引用,但 null == null
产生 true,所以我猜在某些时候内部比较异常字段,第一种情况在整个地方产生空值(请记住,这些异常是通用的,因为它们是由 Scalacheck 组成的),第二种情况具有实际异常(更具体地说,java.lang.NumberFormatException
),其中包含实际对象它们,这导致看似相同的异常在相等比较时产生错误。尝试将测试条件从 fst == snd
更改为 fst.toString == snd.toString
,您会发现两种情况都会通过。
很抱歉没有提供 100% 完整的答案,但我需要投入大量时间来调试愚蠢的 Java 对象和引用的处理以及如何在各个级别实现平等 类 的例外,更不用说整个 "null == null is true" 哲学困境了。如果您(像我一样)对 Java 怪癖没那么感兴趣,那么我想这可以回答您的问题。