2012-05-09 28 views
16

Hãy xem xét các định nghĩa sau đây của một thể loại:cao theo thứ tự ScalaCheck

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

Dưới đây là một ví dụ cho các chức năng unary:

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

Bây giờ, danh mục này tùy thuộc vào một số luật. Liên quan tổng hợp (.) và bản sắc (id):

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

Tôi muốn thử nghiệm này với ScalaCheck. Chúng ta hãy thử cho các chức năng trên số nguyên:

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

Nhưng chúng được định lượng trên (i) một loại hình cụ thể (Int), và (ii) một chức năng cụ thể (intG). Vì vậy, đây là câu hỏi của tôi: làm thế nào đến nay tôi có thể đi về tổng quát các bài kiểm tra ở trên, và làm thế nào? Hay nói cách khác, liệu có thể tạo ra một máy phát điện có chức năng tùy ý A => B và cung cấp các chức năng đó cho ScalaCheck không?

+2

Tôi không biết câu trả lời chính xác cho câu hỏi của bạn, nhưng nó nhắc tôi về việc kiểm tra các quy tắc đơn lẻ trong đại lượng. Có lẽ bạn có thể lấy cảm hứng từ https://github.com/scalaz/scalaz/blob/master/tests/src/test/scala/scalaz/MonadTest.scala –

+2

có lẽ http://stackoverflow.com/users/53013/daniel -c-sobral biết câu trả lời? –

+1

Nếu loại được chọn tùy ý thì bạn có thể xem đây là định lượng phổ thông qua epsilon của Hilbert. Xem https://gist.github.com/2659013. –

Trả lời

5

Không biết chính xác với epsilon của Hilbert, tôi sẽ thực hiện một cách tiếp cận cơ bản hơn và sử dụng ArbitraryGen của ScalaCheck để chọn các chức năng sử dụng.

Đầu tiên, hãy xác định lớp cơ sở cho các hàm bạn sắp tạo. Nói chung, có thể tạo ra các hàm có kết quả không xác định (chẳng hạn như chia cho số không), vì vậy chúng tôi sẽ sử dụng PartialFunction làm lớp cơ sở của chúng tôi.

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

Bây giờ bạn có thể cung cấp một số triển khai. Ghi đè toString để thông báo lỗi của ScalaCheck dễ hiểu.

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. 

Tôi đã chọn tạo các hàm đơn nhất từ ​​các hàm nhị phân sử dụng các kiểu chữ, chuyển các đối số bổ sung cho hàm tạo. Không phải là cách duy nhất để làm điều đó, nhưng tôi thấy nó đơn giản nhất.

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. 

Bây giờ bạn cần để tạo ra một máy phát điện của Fn[Int, Int], và xác định rằng như ngầm Arbitrary[Fn[Int, Int]]. Bạn có thể tiếp tục thêm các trình tạo cho đến khi bạn có màu xanh trên mặt (các đa thức, soạn các hàm phức tạp từ các hàm đơn giản, v.v.).

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) 

Bây giờ bạn có thể xác định thuộc tính của mình. Sử dụng intG.isDefinedAt(a) để tránh các kết quả không xác định.

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

Trong khi những gì tôi chỉ hiển thị tổng quát chức năng được thử nghiệm, hy vọng điều này sẽ cung cấp cho bạn ý tưởng về cách sử dụng thủ thuật hệ thống loại nâng cao để khái quát hóa loại.

Các vấn đề liên quan