2012-05-09 10 views
16

は、カテゴリの次の定義を考えてみましょう:高次ScalaCheck

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

+2

あなたの質問に対する正確な答えはわかりませんが、スカラズのモナド法のチェックを思い出させます。おそらく、あなたはhttps://github.com/scalaz/scalaz/blob/master/tests/src/test/scala/scalaz/MonadTest.scala –

+2

おそらくhttp://stackoverflow.com/users/53013/danielからインスピレーションを得ることができます-c-sobralは答えを知っていますか? –

+1

タイプが任意に選択された場合、ヒルベルトのイプシロンを介した普遍的な定量としてこれを見ることができます。 https://gist.github.com/2659013を参照してください。 –

答えて

5

ヒルベルトのイプシロンを正確に知らないと、私はより基本的なアプローチを取って、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)) 
} 

私は唯一のテスト機能を一般示さきたものが、うまくいけば、これはあなたのタイプの上に一般化する高度な型システムの策略を使用する方法についてのアイデアを与えるだろう。