2012-05-09 9 views
16

Betrachten Sie die folgende Definition einer Kategorie:hohe Ordnung Scalacheck

trait Category[~>[_, _]] { 
    def id[A]: A ~> A 
    def compose[A, B, C](f: A ~> B)(g: B ~> C): A ~> C 
} 

Hier ist ein Beispiel für einstellige Funktionen:

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

Nun Kategorien unterliegen einige Gesetze. Im Zusammenhang Zusammensetzung (.) und Identität (id):

forall f: categoryArrow -> id . f == f . id == f 

Ich möchte dies testen, mit Scalacheck. Lassen Sie uns versuchen, für Funktionen über ganze Zahlen:

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

Diese aber sind quantifiziert über (i) einen bestimmten Typ (Int) und (ii) eine spezifische Funktion (intG). Also, hier ist meine Frage: Wie weit kann ich in Bezug auf die Verallgemeinerung der oben genannten Tests gehen, und wie? Oder mit anderen Worten, wäre es möglich, einen Generator von beliebigen A => B Funktionen zu erstellen und diese an ScalaCheck zu liefern?

+2

Ich weiß nicht die genaue Antwort auf Ihre Frage, aber es erinnert mich an die Überprüfung der Monaden Gesetze in Scalaz. Vielleicht können Sie Inspiration von https://github.com/scalaz/scalaz/blob/master/tests/src/test/scala/scalaz/MonadTest.scala –

+2

vielleicht http://stackoverflow.com/users/53013/daniel nehmen -c-sbral kennt die Antwort? –

+1

Wenn der Typ willkürlich gewählt wird, könnte man dies als universelle Quantifizierung über Hilberts Epsilon betrachten. Siehe https://gist.github.com/2659013. –

Antwort

5

Nicht genau wissend mit Hilberts Epsilon ist, würde ich einen grundlegenderen Ansatz nehmen und ScalaChecks Arbitrary und Gen verwenden, um Funktionen zu wählen, um zu verwenden.

Definieren Sie zuerst eine Basisklasse für die Funktionen, die Sie generieren werden. Im Allgemeinen ist es möglich, Funktionen zu generieren, die undefinierte Ergebnisse haben (z. B. Division durch Null). Daher verwenden wir PartialFunction als Basisklasse.

Jetzt können Sie einige Implementierungen bereitstellen. Überschreiben Sie toString, damit ScalaChecks Fehlermeldungen verständlich sind.

Ich habe gewählt, um unäre Funktionen aus binären Funktionen mit Hilfe von Fallklassen zu generieren und zusätzliche Argumente an den Konstruktor zu übergeben. Nicht der einzige Weg, es zu tun, aber ich finde es am einfachsten.

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. 

Jetzt müssen Sie einen Generator Fn[Int, Int] erstellen und festlegen, dass als implizite Arbitrary[Fn[Int, Int]]. Sie können Generatoren hinzufügen, bis Sie blau im Gesicht sind (Polynome, komplizierte Funktionen aus den einfachen zusammensetzen, usw.).

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) 

Jetzt können Sie Ihre Eigenschaften definieren. Verwenden Sie intG.isDefinedAt(a), um undefinierte Ergebnisse zu vermeiden.

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

Während das, was ich nur gezeigt habe die Funktion getestet verallgemeinert, dies wird hoffentlich geben Ihnen eine Idee, wie fortschrittliche Art System Tricks verwenden, um über die Art zu verallgemeinern.