Scalacheck尝试:Monadic Associativity法通过生成的函数

时间:2018-03-12 10:52:02

标签: scala scalacheck

当我运行以下属性时,它会通过:

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不是monadic(同样适用于“左侧身份”)

当没有生成函数时,它按预期工作,属性失败:

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.

为什么它不适用于生成的函数?

1 个答案:

答案 0 :(得分:4)

首先,monadic associativity law 应该通过这里。 Try仅违反左侧身份法:

unit(x).flatMap(f) == f(x)

因为Try永远不会抛出在其中发生的异常(这是设计的;左边的身份是为了更安全而自愿交易)。因此,如果f抛出异常,左侧将是失败的尝试,右侧将会爆炸。

但是相关性法则:

m.flatMap(f).flatMap(g) == m.flatMap(x ⇒ f(x).flatMap(g))

应该坚持。双方都应该每次成功或失败,但是不可能炸掉一方而不是另一方,因为fg都位于两侧的平面地图内。 / p>

这里发生的是你的第二个片段,即你自己定义函数的片段,会抛出不同类型的异常。如果您只是打印出正在发生的事情:

...
  {
    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提供了一个奇怪的,填充汉字的字符串;这种情况在第一种情况下从未发生过。这与Scalacheck生成测试用例的方式有关。给定函数String => Try[Int],它将提出有效的整数或构成一些通用异常以创建失败案例。它不会使用在String上定义的具体函数(例如您使用的toInt)。

因此,第二种情况会导致数字格式异常。为什么第一个场景中的异常在每次比较时都会产生true,而第二个场景中的数字格式异常会在比较时产生false?我将把它留给Java大师。我认为这与==依赖于Java equals这一事实有关,后者比较了引用,但是null == null产生了真,所以我想某些内部字段是比较异常,第一个场景在所有地方产生空值(记住,这些异常是一般的,因为它们由Scalacheck组成),第二个场景具有实际异常(更具体地,java.lang.NumberFormatException),其中包含实际对象,这导致看似相同的异常在平等比较时产生错误。尝试将测试条件从fst == snd更改为fst.toString == snd.toString,您会看到两种情况都会通过。

很抱歉没有提供100%完整的答案,但我需要投入大量时间来调试对象和引用的愚蠢Java处理以及如何在各种级别和异常类上实现相等性,更不用说整体" null == null为真"哲学困境。如果您对Java怪癖不感兴趣(像我一样),那么我想这会回答您的问题。