【问题标题】:High-Order ScalaCheck高阶 ScalaCheck
【发布时间】:2012-05-09 14:19:33
【问题描述】:

考虑以下类别的定义:

trait Category[~>[_, _]] {
  def id[A]: A ~> A
  def compose[A, B, C](f: A ~> B)(g: B ~> C): A ~> C
}

这是一元函数的一个实例:

object Category {
  implicit def fCat = new Category[Function1] {
    def id[A] = identity
    def compose[A, B, C](f: A => B)(g: B => C) = g.compose(f)
  }
}

现在,类别受某些法律的约束。关联组合 (.) 和身份 (id):

forall f: categoryArrow -> id . f == f . id == f

我想用 ScalaCheck 对此进行测试。让我们尝试整数函数:

"Categories" should {
  import Category._

  val intG  = { (_ : Int) - 5 }

  "left identity" ! check {
    forAll { (a: Int) => fCat.compose(fCat.id[Int])(intG)(a) == intG(a) }      
  }

  "right identity" ! check {
    forAll { (a: Int) => fCat.compose(intG)(fCat.id)(a) == intG(a) }      
  }
}

但这些是通过 (i) 特定类型 (Int) 和 (ii) 特定函数 (intG) 量化的。所以这是我的问题:在概括上述测试方面我能走多远,以及如何?或者,换句话说,是否有可能创建任意 A => B 函数的生成器,并将其提供给 ScalaCheck?

【问题讨论】:

标签: scala specifications category-theory scalacheck


【解决方案1】:

不完全了解 Hilbert 的 epsilon 是,我会采用更基本的方法并使用 ScalaCheck 的 ArbitraryGen 来选择要使用的函数。

首先,为要生成的函数定义一个基类。一般来说,可能会生成结果未定义的函数(例如除以零),因此我们将使用PartialFunction 作为我们的基类。

trait Fn[A, B] extends PartialFunction[A, B] {
  def isDefinedAt(a: A) = true
}

现在您可以提供一些实现。覆盖 toString 以便 ScalaCheck 的错误消息易于理解。

object Identity extends Fn[Int, Int] {
  def apply(a: Int) = a
  override def toString = "a"
}
object Square extends Fn[Int, Int] {
  def apply(a: Int) = a * a
  override def toString = "a * a"
}
// etc.

我选择使用案例类从二进制函数生成一元函数,并将附加参数传递给构造函数。不是唯一的方法,但我发现它是最直接的。

case class Summation(b: Int) extends Fn[Int, Int] {
  def apply(a: Int) = a + b
  override def toString = "a + %d".format(b)
}
case class Quotient(b: Int) extends Fn[Int, Int] {
  def apply(a: Int) = a / b
  override def isDefinedAt(a: Int) = b != 0
  override def toString = "a / %d".format(b)
}
// etc.

现在您需要创建Fn[Int, Int] 的生成器,并将其定义为隐式Arbitrary[Fn[Int, Int]]。你可以继续添加生成器,直到你脸色发青(多项式、从简单函数组合复杂函数等)。

val funcs = for {
  b <- arbitrary[Int]
  factory <- Gen.oneOf[Int => Fn[Int, Int]](
    Summation(_), Difference(_), Product(_), Sum(_), Quotient(_),
    InvDifference(_), InvQuotient(_), (_: Int) => Square, (_: Int) => Identity)
} yield factory(b)

implicit def arbFunc: Arbitrary[Fn[Int, Int]] = Arbitrary(funcs)

现在您可以定义您的属性。使用intG.isDefinedAt(a) 来避免未定义的结果。

property("left identity simple funcs") = forAll { (a: Int, intG: Fn[Int, Int]) =>
  intG.isDefinedAt(a) ==> (fCat.compose(fCat.id[Int])(intG)(a) == intG(a))
}

property("right identity simple funcs") =  forAll { (a: Int, intG: Fn[Int, Int]) =>
  intG.isDefinedAt(a) ==> (fCat.compose(intG)(fCat.id)(a) == intG(a))
}

虽然我展示的只是概括了测试的函数,但希望这能让您了解如何使用高级类型系统技巧来概括该类型。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-09-03
    • 1970-01-01
    • 1970-01-01
    • 2019-05-16
    • 2016-05-07
    • 2010-12-28
    相关资源
    最近更新 更多