Search code examples
scalascalatestscalacheck

ScalaCheck: generate arbitrary functions with arbitrary types


I have implemented the following function:

/**
  * Returns a function h , which is the composition of the functions f and g.
  */
def compose[A, B, C](g: B => C, f: A => B): A => C = f.andThen(g)

And I'm trying to test it with ScalaCheck. I could generate the following tests, which compile and pass:

import org.scalatest.prop.PropertyChecks
import org.scalatest.{FlatSpec, Matchers}

class ComposeSpec extends FlatSpec with Matchers with PropertyChecks {

  "Compose" should "return a function h , which is the composition of the 
functions f and g" in {

    forAll { (a: Int, g: Int => Int, f: Int => Int) =>
      compose(g, f)(a) should be(g(f(a)))
    }

    forAll { (a: String, g: Double => Int, f: String => Double) =>
      compose(g, f)(a) should be(g(f(a)))
    }
  }
}

But, as you see, I'm generating arbitrary functions with defined types, and also matching the type of the parameter a with the type of the input of the function f. What I want to do would be something like this:

forAll { (a: A, g: B => C, f: A => B) =>
  compose(g, f)(a) should be(g(f(a)))
}

But I don't know the syntax for that, nor if it's possible. Could you help me?


Solution

  • The scalatest website has this to say about forAll:

    An implicit Arbitrary generator and Shrink object needs to be supplied for The forAll method will pass each row of data to each parameter type. ScalaCheck provides many implicit Arbitrary generators for common types such as Int, String, List[Float], etc., in its org.scalacheck.Arbitrary companion object. So long as you use types for which ScalaCheck already provides implicit Arbitrary generators, you needn't worry about them. Same for Shrink objects, which are provided by ScalaCheck's org.scalacheck.Shrink companion object. Most often you can simply pass a property function to forAll, and the compiler will grab the implicit values provided by ScalaCheck.

    So unfortunately you can't use forAll to check every possible type, because there are no implicit Arbitrary and Shrink objects for every possible type. It seems pretty impossible to generate arbitrary objects of any type.

    The best you could do would be something like:

    def checkComposeForAll[A : Arbitrary : Shrink, B : Arbitrary : Shrink, C : Arbitrary : Shrink]() = {
      forAll { (a: A, g: B => C, f: A => B) =>
        compose(g, f)(a) should be(g(f(a)))
      }
    }
    
    checkComposeForAll[Int, Int, Int]()
    checkComposeForAll[String, String, String]()
    checkComposeForAll[List[Int], Double, Int]()
    // ... etc, check a bunch of types ...