Function generation creates test inputs that are themselves functions. It matters when a property quantifies over a higher-order argument: for any list and predicate, every element returned by takeWhile should satisfy that predicate. Testing only hand-picked predicates leaves the testing framework unable to explore that argument space. section 8.3 section 8.6
A generator for output values can trivially produce a function that ignores its input: generate an Int, then return a String => Int that always returns it. Such constant functions are valid but weak test cases. For predicates, they amount to functions that always return true or always return false, which cannot probe where takeWhile stops. section 8.6
The essential move is to make each generated function deterministic while letting its input influence the generated result. First obtain private random information when constructing the function. Later, when the function receives an argument, derive an RNG from that information and the argument, then run the output generator with the derived RNG. The same argument therefore gives the same result, while different arguments can select different generated values. section 8.6
From String to generated output
Given Gen[A], genStringFn produces Gen[String => A]. It draws random state once, returns a function, and has that function combine its String input with the captured random state before running Gen[A]. The output type remains fully generic, so the technique does not rely on manufacturing an A directly from a String. section 8.6
General function generation separates the two responsibilities. A Cogen[A] perturbs an RNG using an input A; a Gen[B] produces an output B from the perturbed RNG. Combining them yields Gen[A => B]. This avoids requiring a hash-like operation to return the output type and makes the input-dependence strategy explicit. section 8.6
def fn[A, B](in: Cogen[A], out: Gen[B]): Gen[A => B] =
State[RNG, A => B]: rng =>
val (seed, rng2) = rng.nextInt
val f = (a: A) => out.run(in(a, rng2))(0)
(f, rng2)
The generalized constructor shows how an input perturbation and an output generator combine into a generator of functions. section 8.6
Use function generation to strengthen higher-order properties:
- Generate predicates for takeWhile rather than restricting tests to a single predicate such as isEven. section 8.3 section 8.6
- Check both that the returned prefix satisfies the predicate and that the next remaining element, when present, fails it. section 8.6
- Relate takeWhile and dropWhile by checking that their results concatenate to the original list. section 8.6
