2012-05-09 22 views
16

एक वर्ग की निम्नलिखित परिभाषा पर विचार करें। संबंधित संरचना (.) और पहचान (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) }  
    } 
} 

लेकिन इन (i) एक विशेष प्रकार (Int), और (ii) एक विशेष समारोह (intG) से अधिक मात्रा निर्धारित कर रहे हैं। तो यहां मेरा प्रश्न है: उपरोक्त परीक्षणों को सामान्यीकृत करने के संदर्भ में मैं कितनी दूर जा सकता हूं, और कैसे? या, दूसरे शब्दों में, क्या मनमाने ढंग से A => B कार्यों का जनरेटर बनाना संभव होगा, और उन्हें स्कैला चेक प्रदान करें?

+2

मुझे आपके प्रश्न का सटीक उत्तर नहीं पता है, लेकिन यह मुझे स्कालज़ में मोनैड कानूनों के चेक की याद दिलाता है। शायद आप https://github.com/scalaz/scalaz/blob/master/tests/src/test/scala/scalaz/MonadTest.scala –

+2

से प्रेरणा ले सकते हैं शायद http://stackoverflow.com/users/53013/daniel -सी-सोब्राल जवाब जानता है? –

+1

यदि प्रकार मनमाने ढंग से चुना जाता है तो आप इसे हिल्बर्ट के ईपीएसलॉन के माध्यम से सार्वभौमिक मात्रा के रूप में देख सकते हैं। Https://gist.github.com/2659013 देखें। –

उत्तर

5

हिल्बर्ट के ईपीएसलॉन के साथ बिल्कुल नहीं जानना है, मैं अधिक मौलिक दृष्टिकोण लेता हूं और उपयोग करने के लिए कार्यों का चयन करने के लिए स्कैला चेक के Arbitrary और Gen का उपयोग करता हूं।

सबसे पहले, उन कार्यों के लिए बेस क्लास परिभाषित करें जिन्हें आप उत्पन्न करने जा रहे हैं। आम तौर पर, उन कार्यों को उत्पन्न करना संभव होता है जिनमें अपरिभाषित परिणाम होते हैं (जैसे कि शून्य से विभाजित), इसलिए हम PartialFunction का उपयोग हमारी बेस क्लास के रूप में करेंगे।

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

अब आप कुछ कार्यान्वयन प्रदान कर सकते हैं। toString ओवरराइड करें ताकि स्कैला चेक के त्रुटि संदेश समझदार हों।

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

जबकि मैं क्या दिखाया है केवल समारोह का परीक्षण किया सामान्यीकरण करता, उम्मीद है कि यह आप कैसे उन्नत प्रकार प्रणाली प्रवंचना का उपयोग करने के प्रकार से अधिक सामान्यीकरण करने के लिए पर एक विचार दे देंगे।