2012-05-09 8 views
16

는 범주의 다음과 같은 정의를 생각해 보자. 관련 성분 (.) 및 ID (id) :고 주문 ScalaCheck

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

그러나 이것들은 (ⅰ) 특정 유형 (Int), 및 (ii) 특정 기능 (intG)를 통해 정량화된다. 여기에 제 질문이 있습니다 : 위의 테스트를 일반화하는 관점에서 얼마나 멀리 갈 수 있습니까? 또는 다른 말로하면, 임의의 A => B 함수의 생성자를 생성하여 ScalaCheck에 제공 할 수 있습니까?

+2

귀하의 질문에 대한 정확한 답변을 모르겠지만 그것은 scalaz의 모나드 법에 대한 수표를 상기시켜줍니다. 아마 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

필자는 Hilbert의 엡실론을 정확히 모르는 상태에서 ScalaCheck의 ArbitraryGen을 사용하여 사용할 함수를 선택했습니다.

먼저 생성하려는 함수의 기본 클래스를 정의하십시오. 일반적으로 정의되지 않은 결과 (예 : 0으로 나누기)가있는 함수를 생성 할 수 있으므로 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)) 
} 

는 희망이 당신에게 유형에 걸쳐 일반화 선진 형 시스템 속임수를 사용하는 방법에 대한 아이디어를 줄 것이다.