17

カテゴリの次の定義を検討してください。

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 に提供することは可能でしょうか?

4

1 に答える 1

6

Hilbert のイプシロンを正確に理解していないので、より基本的なアプローチを取り、ScalaCheckArbitraryGen使用して、使用する関数を選択します。

まず、生成する関数の基本クラスを定義します。一般に、未定義の結果 (0 による除算など) を持つ関数を生成することは可能であるためPartialFunction、基本クラスとして使用します。

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

これで、いくつかの実装を提供できます。toStringScalaCheck のエラー メッセージがわかりやすいようにオーバーライドします。

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))
}

私が示したものはテストされた関数を一般化しただけですが、高度な型システムのトリックを使用して型を一般化する方法についてのアイデアが得られることを願っています。

于 2012-05-21T15:07:43.967 に答える