首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >高阶ScalaCheck

高阶ScalaCheck
EN

Stack Overflow用户
提问于 2012-05-09 22:19:33
回答 1查看 856关注 0票数 17

考虑以下类别的定义:

代码语言:javascript
复制
trait Category[~>[_, _]] {
  def id[A]: A ~> A
  def compose[A, B, C](f: A ~> B)(g: B ~> C): A ~> C
}

下面是一个一元函数的实例:

代码语言:javascript
复制
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):

代码语言:javascript
复制
forall f: categoryArrow -> id . f == f . id == f

我想用ScalaCheck测试一下。让我们尝试一下整数上的函数:

代码语言:javascript
复制
"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?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2012-05-21 23:07:44

如果不确切知道希尔伯特的epsilon是什么,我会采取更基本的方法,使用ScalaCheck的ArbitraryGen来选择要使用的函数。

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

代码语言:javascript
复制
trait Fn[A, B] extends PartialFunction[A, B] {
  def isDefinedAt(a: A) = true
}

现在您可以提供一些实现。重写toString,这样ScalaCheck的错误消息就可以理解了。

代码语言:javascript
复制
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类从二进制函数生成一元函数,并将额外的参数传递给构造函数。这不是唯一的方法,但我发现它是最简单的。

代码语言:javascript
复制
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]]。你可以继续添加生成器,直到你脸色发青(多项式,从简单的函数组成复杂的函数,等等)。

代码语言:javascript
复制
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)避免未定义的结果。

代码语言:javascript
复制
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))
}

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

票数 6
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/10518015

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档