2012-05-09 52 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) }  
    } 
} 

但是,這些被量化過(我)特定類型(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

如果類型是任意選擇的,那麼您可以將其視爲通過Hilbert's epsilon的通用量化。請參閱https://gist.github.com/2659013。 –

回答

5

不知道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類生成一元函數,將附加參數傳遞給構造函數。不是唯一的辦法,但我覺得它是最直接的。

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

雖然我已經證明只是概括測試的功能,希望這會給你如何使用先進的類型系統欺騙概括了類型的想法。