summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main/java/au/id/zancanaro/javacheck/Generator.java89
1 files changed, 87 insertions, 2 deletions
diff --git a/src/main/java/au/id/zancanaro/javacheck/Generator.java b/src/main/java/au/id/zancanaro/javacheck/Generator.java
index 2d2e556..4b9113c 100644
--- a/src/main/java/au/id/zancanaro/javacheck/Generator.java
+++ b/src/main/java/au/id/zancanaro/javacheck/Generator.java
@@ -4,13 +4,62 @@ import java.util.List;
import java.util.Random;
import java.util.function.Function;
+/**
+ * Generators are a way of producing random objects and their associated shrink
+ * trees in a controlled and deterministic way.
+ *
+ * A generator must implement one method: {@link #generate(Random, int)}. The
+ * {@link RoseTree} it produces defines both the value to be returned, as well
+ * as the shrink tree for that value.
+ *
+ * Generators form a "monad", and hence have the {@link #map(Function)} and
+ * {@link #flatMap(Function)} methods for composition. The helper methods {@link
+ * #pure(Object)}, {@link #tuple(Generator[])} and {@link #list(int, Generator)}
+ * allow for composition at a lower level than the monadic actions..
+ *
+ * @param <T> The type generated by this generator.
+ */
public interface Generator<T> {
+ /**
+ * Return a {@link RoseTree} containing a new random value of the required
+ * type, as well as its associated shrink tree.
+ *
+ * Generators also have an abstract notion of "size". Represented by an
+ * integer, generators should use this as a guide for how large an object to
+ * generate. There is no specific meaning for this value, so the precise
+ * interpretation will depend on the specifics of the generator.
+ *
+ * @param random The random source for generation purposes
+ * @param size An integer specifying how "big" a thing to produce
+ * @return The {@link RoseTree} specifying the generated thing and its
+ * shrink tree
+ */
RoseTree<T> generate(Random random, int size);
+ /**
+ * A generator which simply generates the provided value. Does not shrink.
+ *
+ * @param value The value to generate
+ * @param <T> The type of the generated value
+ * @return A {@link Generator} which generates the provided value
+ */
static <T> Generator<T> pure(T value) {
return (random, size) -> RoseTree.pure(value);
}
+ /**
+ * A generator which generates a {@link List}, with each element taken from
+ * its corresponding generator. (That is: return[i] =
+ * generators[i].generate(...).)
+ *
+ * Shrinking for this type involves attempting to shrink each subtree in
+ * turn, recursively.
+ *
+ * @param generators The generators to use for each term in the generated
+ * result
+ * @param <T> The parameter type of the generated {@link List}
+ * @return A {@link Generator} returning a {@link List}
+ */
@SafeVarargs
static <T> Generator<List<T>> tuple(Generator<T>... generators) {
return (random, size) -> {
@@ -24,21 +73,57 @@ public interface Generator<T> {
};
}
- static <T> Generator<List<T>> list(int count, Generator<T> gen) {
+ /**
+ * A generator which generates a {@link List} of length count, with each
+ * element taken from the provided generator.
+ *
+ * Shrinking for this type involves attempting to remove terms and shrink
+ * each subtree in turn, recursively. (If the length must remain fixed then
+ * the {@link RoseTree} produced by this generator should be filtered with
+ * {@link RoseTree#filter(java.util.function.Predicate)}.
+ *
+ * @param count The length of the list to generate
+ * @param generator The generator to use for each term in the generated
+ * result
+ * @param <T> The parameter type of the generated {@link List}
+ * @return A {@link Generator} returning a {@link List}
+ */
+ static <T> Generator<List<T>> list(int count, Generator<T> generator) {
return (random, size) -> {
@SuppressWarnings("unchecked")
RoseTree<T>[] result = (RoseTree<T>[]) new RoseTree[count];
for (int i = 0; i < count; ++i) {
- result[i] = gen.generate(random, size);
+ result[i] = generator.generate(random, size);
}
return RoseTree.shrink(Function.identity(), result);
};
}
+ /**
+ * Transform the result of a generator by passing it through a function.
+ *
+ * Maps all values in the shrink of this through f, as well.
+ *
+ * @param f The transformation function
+ * @param <R> The result of the transformation
+ * @return A new generator resulting from mapping f over this
+ */
default <R> Generator<R> map(Function<T, R> f) {
return (random, size) -> this.generate(random, size).map(f);
}
+ /**
+ * Produce a new generator relying on the value generated by this generator
+ *
+ * Shrinking is a bit hard to predict under {@link #flatMap}, as it will
+ * first attempt to shrink this, resulting in the re-evaluation of action,
+ * and hence the re-generation of the subtree.
+ *
+ * @param action A function to produce the new generator
+ * @param <R> The type of the returned generator
+ * @return A new generator resulting from calling the provided action on the
+ * result of this
+ */
default <R> Generator<R> flatMap(Function<T, Generator<R>> action) {
return (random, size) -> RoseTree.join(this.generate(random, size).map(action).map(g -> g.generate(random, size)));
}