All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.jqwik.api.Combinators Maven / Gradle / Ivy

There is a newer version: 1.9.2
Show newest version
package net.jqwik.api;

import java.util.*;
import java.util.function.*;
import java.util.stream.*;

import org.apiguardian.api.*;

import static org.apiguardian.api.API.Status.*;

@API(status = MAINTAINED, since = "1.0")
public class Combinators {

	@API(status = INTERNAL)
	public static abstract class CombinatorsFacade {
		private static final CombinatorsFacade implementation;

		static {
			implementation = FacadeLoader.load(CombinatorsFacade.class);
		}

		public abstract  Shrinkable combineShrinkables(
			List> shrinkables,
			Function, R> combineFunction
		);

		public abstract  Optional> combineExhaustive(
			List> arbitraries,
			Function, R> combineFunction,
			long maxNumberOfSamples
		);

		public abstract  EdgeCases combineEdgeCases(
				List> arbitraries,
				Function, R> combineFunction,
				int maxEdgeCases
		);
	}

	private Combinators() {
	}

	/**
	 * Combine 2 arbitraries into one.
	 *
	 * @return Combinator2 instance which can be evaluated using {@linkplain Combinator2#as}
	 */
	public static  Combinator2 combine(Arbitrary a1, Arbitrary a2) {
		return new Combinator2<>(a1, a2);
	}

	/**
	 * Combine 3 arbitraries into one.
	 *
	 * @return Combinator3 instance which can be evaluated using {@linkplain Combinator3#as}
	 */
	public static  Combinator3 combine(Arbitrary a1, Arbitrary a2, Arbitrary a3) {
		return new Combinator3<>(a1, a2, a3);
	}

	/**
	 * Combine 4 arbitraries into one.
	 *
	 * @return Combinator4 instance which can be evaluated using {@linkplain Combinator4#as}
	 */
	public static  Combinator4 combine(
		Arbitrary a1, Arbitrary a2, Arbitrary a3,
		Arbitrary a4
	) {
		return new Combinator4<>(a1, a2, a3, a4);
	}

	/**
	 * Combine 5 arbitraries into one.
	 *
	 * @return Combinator5 instance which can be evaluated using {@linkplain Combinator5#as}
	 */
	public static  Combinator5 combine(
		Arbitrary a1, Arbitrary a2, Arbitrary a3,
		Arbitrary a4, Arbitrary a5
	) {
		return new Combinator5<>(a1, a2, a3, a4, a5);
	}

	/**
	 * Combine 6 arbitraries into one.
	 *
	 * @return Combinator6 instance which can be evaluated using {@linkplain Combinator6#as}
	 */
	public static  Combinator6 combine(
		Arbitrary a1, Arbitrary a2, Arbitrary a3,
		Arbitrary a4, Arbitrary a5, Arbitrary a6
	) {
		return new Combinator6<>(a1, a2, a3, a4, a5, a6);
	}

	/**
	 * Combine 7 arbitraries into one.
	 *
	 * @return Combinator7 instance which can be evaluated using {@linkplain Combinator7#as}
	 */
	public static  Combinator7 combine(
		Arbitrary a1, Arbitrary a2,
		Arbitrary a3, Arbitrary a4, Arbitrary a5, Arbitrary a6, Arbitrary a7
	) {
		return new Combinator7<>(a1, a2, a3, a4, a5, a6, a7);
	}

	/**
	 * Combine 8 arbitraries into one.
	 *
	 * @return Combinator8 instance which can be evaluated using {@linkplain Combinator8#as}
	 */
	public static  Combinator8 combine(
		Arbitrary a1, Arbitrary a2,
		Arbitrary a3, Arbitrary a4, Arbitrary a5, Arbitrary a6, Arbitrary a7, Arbitrary a8
	) {
		return new Combinator8<>(a1, a2, a3, a4, a5, a6, a7, a8);
	}

	/**
	 * Combine a list of arbitraries into one.
	 *
	 * @return ListCombinator instance which can be evaluated using {@linkplain ListCombinator#as}
	 */
	public static  ListCombinator combine(List> listOfArbitraries) {
		return new ListCombinator<>(listOfArbitraries);
	}

	/**
	 * Combine Arbitraries by means of a builder.
	 *
	 * @param builderSupplier The supplier will be called freshly for each value generation.
	 *                        For exhaustive generation all supplied objects are
	 *                        supposed to be identical.
	 * @return BuilderCombinator instance
	 */
	@API(status = MAINTAINED, since = "1.2.0")
	public static  BuilderCombinator withBuilder(Supplier builderSupplier) {
		return new BuilderCombinator<>(Arbitraries.create(builderSupplier));
	}

	/**
	 * Combine Arbitraries by means of a builder.
	 *
	 * @param builderArbitrary The arbitrary is used to generate a builder object
	 *                         as starting point for building on each value generation.
	 * @return BuilderCombinator instance
	 */
	@API(status = MAINTAINED, since = "1.2.0")
	public static  BuilderCombinator withBuilder(Arbitrary builderArbitrary) {
		return new BuilderCombinator<>(builderArbitrary);
	}

	@SuppressWarnings("unchecked")
	private static  Function, R> combineFunction(F2 combinator2) {
		return params -> combinator2
							 .apply((T1) params.get(0), (T2) params.get(1));
	}

	@SuppressWarnings("unchecked")
	private static  Function, R> combineFunction(F3 combinator3) {
		return params -> combinator3
							 .apply((T1) params.get(0), (T2) params.get(1), (T3) params.get(2));
	}

	@SuppressWarnings("unchecked")
	private static  Function, R> combineFunction(F4 combinator4) {
		return params -> combinator4
							 .apply(
								 (T1) params.get(0), (T2) params.get(1),
								 (T3) params.get(2), (T4) params.get(3)
							 );
	}

	@SuppressWarnings("unchecked")
	private static  Function, R> combineFunction(F5 combinator5) {
		return params -> combinator5
							 .apply(
								 (T1) params.get(0), (T2) params.get(1),
								 (T3) params.get(2), (T4) params.get(3),
								 (T5) params.get(4)
							 );
	}

	@SuppressWarnings("unchecked")
	private static  Function, R> combineFunction(F6 combinator6) {
		return params -> combinator6
							 .apply(
								 (T1) params.get(0), (T2) params.get(1),
								 (T3) params.get(2), (T4) params.get(3),
								 (T5) params.get(4), (T6) params.get(5)
							 );
	}

	@SuppressWarnings("unchecked")
	private static  Function, R> combineFunction(F7 combinator7) {
		return params -> combinator7
							 .apply(
								 (T1) params.get(0), (T2) params.get(1),
								 (T3) params.get(2), (T4) params.get(3),
								 (T5) params.get(4), (T6) params.get(5),
								 (T7) params.get(6)
							 );
	}

	@SuppressWarnings("unchecked")
	private static  Function, R> combineFunction(F8 combinator8) {
		return params -> combinator8
							 .apply(
								 (T1) params.get(0), (T2) params.get(1),
								 (T3) params.get(2), (T4) params.get(3),
								 (T5) params.get(4), (T6) params.get(5),
								 (T7) params.get(6), (T8) params.get(7)
							 );
	}

	@SuppressWarnings("unchecked")
	private static  List asTypedList(Object... objects) {
		List list = new ArrayList<>();
		for (Object object : objects) {
			list.add((T) object);
		}
		return list;
	}

	/**
	 * Combinator for two values.
	 */
	public static class Combinator2 {
		private final Arbitrary a1;
		private final Arbitrary a2;

		private Combinator2(Arbitrary a1, Arbitrary a2) {
			this.a1 = a1;
			this.a2 = a2;
		}

		/**
		 * Combine two values.
		 *
		 * @param combinator function
		 * @param  return type
		 * @return arbitrary instance
		 */
		public  Arbitrary as(F2 combinator) {
			// This is a shorter implementation of as, which however would have worse shrinking
			// behaviour because it builds on flatMap:
			//		public  Arbitrary as(F2 combinator) {
			//			return a1.flatMap(v1 -> a2.map(v2 -> combinator.apply(v1, v2)));
			//		}
			return new Arbitrary() {
				@Override
				public RandomGenerator generator(int genSize) {
					return rawGenerator(genSize, false);
				}

				@Override
				public RandomGenerator generatorWithEmbeddedEdgeCases(int genSize) {
					return rawGenerator(genSize, true);
				}

				private RandomGenerator rawGenerator(int genSize, boolean withEmbeddedEdgeCases) {
					RandomGenerator g1 = a1.generator(genSize, withEmbeddedEdgeCases);
					RandomGenerator g2 = a2.generator(genSize, withEmbeddedEdgeCases);
					return random -> {
						List> shrinkables = asTypedList(g1.next(random), g2.next(random));
						return CombinatorsFacade.implementation.combineShrinkables(shrinkables, combineFunction(combinator));
					};
				}

				@Override
				public Optional> exhaustive(long maxNumberOfSamples) {
					return CombinatorsFacade.implementation.combineExhaustive(
						asTypedList(a1, a2),
						combineFunction(combinator),
						maxNumberOfSamples
					);
				}

				@Override
				public EdgeCases edgeCases(int maxEdgeCases) {
					return CombinatorsFacade.implementation.combineEdgeCases(
						asTypedList(a1, a2),
						combineFunction(combinator),
						maxEdgeCases
					);
				}

			};
		}

		public  Arbitrary flatAs(F2> flatCombinator) {
			return a1.flatMap(v1 -> a2.flatMap(v2 -> flatCombinator.apply(v1, v2)));
		}
	}

	/**
	 * Combinator for three values.
	 */
	public static class Combinator3 {
		private final Arbitrary a1;
		private final Arbitrary a2;
		private final Arbitrary a3;

		private Combinator3(Arbitrary a1, Arbitrary a2, Arbitrary a3) {
			this.a1 = a1;
			this.a2 = a2;
			this.a3 = a3;
		}

		/**
		 * Combine three values.
		 *
		 * @param combinator function
		 * @param  return type
		 * @return arbitrary instance
		 */
		public  Arbitrary as(F3 combinator) {
			return new Arbitrary() {
				@Override
				public RandomGenerator generator(int genSize) {
					return rawGenerator(genSize, false);
				}

				@Override
				public RandomGenerator generatorWithEmbeddedEdgeCases(int genSize) {
					return rawGenerator(genSize, true);
				}

				private RandomGenerator rawGenerator(int genSize, boolean withEmbeddedEdgeCases) {
					RandomGenerator g1 = a1.generator(genSize, withEmbeddedEdgeCases);
					RandomGenerator g2 = a2.generator(genSize, withEmbeddedEdgeCases);
					RandomGenerator g3 = a3.generator(genSize, withEmbeddedEdgeCases);
					return random -> {
						List> shrinkables = asTypedList(
							g1.next(random),
							g2.next(random),
							g3.next(random)
						);
						return CombinatorsFacade.implementation.combineShrinkables(shrinkables, combineFunction(combinator));
					};
				}

				@Override
				public Optional> exhaustive(long maxNumberOfSamples) {
					return CombinatorsFacade.implementation.combineExhaustive(
						asTypedList(a1, a2, a3),
						combineFunction(combinator),
						maxNumberOfSamples
					);
				}

				@Override
				public EdgeCases edgeCases(int maxEdgeCases) {
					return CombinatorsFacade.implementation.combineEdgeCases(
						asTypedList(a1, a2, a3),
						combineFunction(combinator),
						maxEdgeCases
					);
				}

			};
		}

		public  Arbitrary flatAs(F3> flatCombinator) {
			return a1.flatMap(
				v1 -> a2.flatMap(
					v2 -> a3.flatMap(
						v3 -> flatCombinator.apply(v1, v2, v3))));
		}

	}

	/**
	 * Combinator for four values.
	 */
	public static class Combinator4 {
		private final Arbitrary a1;
		private final Arbitrary a2;
		private final Arbitrary a3;
		private final Arbitrary a4;

		private Combinator4(Arbitrary a1, Arbitrary a2, Arbitrary a3, Arbitrary a4) {
			this.a1 = a1;
			this.a2 = a2;
			this.a3 = a3;
			this.a4 = a4;
		}

		/**
		 * Combine four values.
		 *
		 * @param combinator function
		 * @param  return type
		 * @return arbitrary instance
		 */
		public  Arbitrary as(F4 combinator) {
			return new Arbitrary() {
				@Override
				public RandomGenerator generator(int genSize) {
					return rawGenerator(genSize, false);
				}

				@Override
				public RandomGenerator generatorWithEmbeddedEdgeCases(int genSize) {
					return rawGenerator(genSize, true);
				}

				private RandomGenerator rawGenerator(int genSize, boolean withEmbeddedEdgeCases) {
					RandomGenerator g1 = a1.generator(genSize, withEmbeddedEdgeCases);
					RandomGenerator g2 = a2.generator(genSize, withEmbeddedEdgeCases);
					RandomGenerator g3 = a3.generator(genSize, withEmbeddedEdgeCases);
					RandomGenerator g4 = a4.generator(genSize, withEmbeddedEdgeCases);

					return random -> {
						List> shrinkables = asTypedList(
							g1.next(random),
							g2.next(random),
							g3.next(random),
							g4.next(random)
						);

						return CombinatorsFacade.implementation.combineShrinkables(shrinkables, combineFunction(combinator));
					};
				}

				@Override
				public Optional> exhaustive(long maxNumberOfSamples) {
					return CombinatorsFacade.implementation.combineExhaustive(
						asTypedList(a1, a2, a3, a4),
						combineFunction(combinator),
						maxNumberOfSamples
					);
				}

				@Override
				public EdgeCases edgeCases(int maxEdgeCases) {
					return CombinatorsFacade.implementation.combineEdgeCases(
						asTypedList(a1, a2, a3, a4),
						combineFunction(combinator),
						maxEdgeCases
					);
				}

			};
		}

		public  Arbitrary flatAs(F4> flatCombinator) {
			return a1.flatMap(
				v1 -> a2.flatMap(
					v2 -> a3.flatMap(
						v3 -> a4.flatMap(
							v4 -> flatCombinator.apply(v1, v2, v3, v4)))));
		}

	}

	/**
	 * Combinator for five values.
	 */
	public static class Combinator5 {
		private final Arbitrary a1;
		private final Arbitrary a2;
		private final Arbitrary a3;
		private final Arbitrary a4;
		private final Arbitrary a5;

		private Combinator5(Arbitrary a1, Arbitrary a2, Arbitrary a3, Arbitrary a4, Arbitrary a5) {
			this.a1 = a1;
			this.a2 = a2;
			this.a3 = a3;
			this.a4 = a4;
			this.a5 = a5;
		}

		/**
		 * Combine five values.
		 *
		 * @param combinator function
		 * @param  return type
		 * @return arbitrary instance
		 */
		public  Arbitrary as(F5 combinator) {
			return new Arbitrary() {
				@Override
				public RandomGenerator generator(int genSize) {
					return rawGenerator(genSize, false);
				}

				@Override
				public RandomGenerator generatorWithEmbeddedEdgeCases(int genSize) {
					return rawGenerator(genSize, true);
				}

				private RandomGenerator rawGenerator(int genSize, boolean withEmbeddedEdgeCases) {
					RandomGenerator g1 = a1.generator(genSize, withEmbeddedEdgeCases);
					RandomGenerator g2 = a2.generator(genSize, withEmbeddedEdgeCases);
					RandomGenerator g3 = a3.generator(genSize, withEmbeddedEdgeCases);
					RandomGenerator g4 = a4.generator(genSize, withEmbeddedEdgeCases);
					RandomGenerator g5 = a5.generator(genSize, withEmbeddedEdgeCases);

					return random -> {
						List> shrinkables = asTypedList(
							g1.next(random),
							g2.next(random),
							g3.next(random),
							g4.next(random),
							g5.next(random)
						);
						return CombinatorsFacade.implementation.combineShrinkables(shrinkables, combineFunction(combinator));
					};
				}

				@Override
				public Optional> exhaustive(long maxNumberOfSamples) {
					return CombinatorsFacade.implementation.combineExhaustive(
						asTypedList(a1, a2, a3, a4, a5),
						combineFunction(combinator),
						maxNumberOfSamples
					);
				}

				@Override
				public EdgeCases edgeCases(int maxEdgeCases) {
					return CombinatorsFacade.implementation.combineEdgeCases(
						asTypedList(a1, a2, a3, a4, a5),
						combineFunction(combinator),
						maxEdgeCases
					);
				}

			};
		}

		public  Arbitrary flatAs(F5> flatCombinator) {
			return a1.flatMap(
				v1 -> a2.flatMap(
					v2 -> a3.flatMap(
						v3 -> a4.flatMap(
							v4 -> a5.flatMap(
								v5 -> flatCombinator.apply(v1, v2, v3, v4, v5))))));
		}

	}

	/**
	 * Combinator for six values.
	 */
	public static class Combinator6 {
		private final Arbitrary a1;
		private final Arbitrary a2;
		private final Arbitrary a3;
		private final Arbitrary a4;
		private final Arbitrary a5;
		private final Arbitrary a6;

		private Combinator6(Arbitrary a1, Arbitrary a2, Arbitrary a3, Arbitrary a4, Arbitrary a5, Arbitrary a6) {
			this.a1 = a1;
			this.a2 = a2;
			this.a3 = a3;
			this.a4 = a4;
			this.a5 = a5;
			this.a6 = a6;
		}

		/**
		 * Combine six values.
		 *
		 * @param combinator function
		 * @param  return type
		 * @return arbitrary instance
		 */
		public  Arbitrary as(F6 combinator) {
			return new Arbitrary() {
				@Override
				public RandomGenerator generator(int genSize) {
					return rawGenerator(genSize, false);
				}

				@Override
				public RandomGenerator generatorWithEmbeddedEdgeCases(int genSize) {
					return rawGenerator(genSize, true);
				}

				private RandomGenerator rawGenerator(int genSize, boolean withEmbeddedEdgeCases) {
					RandomGenerator g1 = a1.generator(genSize, withEmbeddedEdgeCases);
					RandomGenerator g2 = a2.generator(genSize, withEmbeddedEdgeCases);
					RandomGenerator g3 = a3.generator(genSize, withEmbeddedEdgeCases);
					RandomGenerator g4 = a4.generator(genSize, withEmbeddedEdgeCases);
					RandomGenerator g5 = a5.generator(genSize, withEmbeddedEdgeCases);
					RandomGenerator g6 = a6.generator(genSize, withEmbeddedEdgeCases);

					return random -> {
						List> shrinkables = asTypedList(
							g1.next(random),
							g2.next(random),
							g3.next(random),
							g4.next(random),
							g5.next(random),
							g6.next(random)
						);
						return CombinatorsFacade.implementation.combineShrinkables(shrinkables, combineFunction(combinator));
					};
				}

				@Override
				public Optional> exhaustive(long maxNumberOfSamples) {
					return CombinatorsFacade.implementation.combineExhaustive(
						asTypedList(a1, a2, a3, a4, a5, a6),
						combineFunction(combinator),
						maxNumberOfSamples
					);
				}

				@Override
				public EdgeCases edgeCases(int maxEdgeCases) {
					return CombinatorsFacade.implementation.combineEdgeCases(
						asTypedList(a1, a2, a3, a4, a5, a6),
						combineFunction(combinator),
						maxEdgeCases
					);
				}

			};
		}

		public  Arbitrary flatAs(F6> flatCombinator) {
			return a1.flatMap(
				v1 -> a2.flatMap(
					v2 -> a3.flatMap(
						v3 -> a4.flatMap(
							v4 -> a5.flatMap(
								v5 -> a6.flatMap(
									v6 -> flatCombinator.apply(v1, v2, v3, v4, v5, v6)))))));
		}

	}

	/**
	 * Combinator for seven values.
	 */
	public static class Combinator7 {
		private final Arbitrary a1;
		private final Arbitrary a2;
		private final Arbitrary a3;
		private final Arbitrary a4;
		private final Arbitrary a5;
		private final Arbitrary a6;
		private final Arbitrary a7;

		private Combinator7(
			Arbitrary a1, Arbitrary a2, Arbitrary a3, Arbitrary a4, Arbitrary a5, Arbitrary a6,
			Arbitrary a7
		) {
			this.a1 = a1;
			this.a2 = a2;
			this.a3 = a3;
			this.a4 = a4;
			this.a5 = a5;
			this.a6 = a6;
			this.a7 = a7;
		}

		/**
		 * Combine seven values.
		 *
		 * @param combinator function
		 * @param  return type
		 * @return arbitrary instance
		 */
		public  Arbitrary as(F7 combinator) {
			return new Arbitrary() {
				@Override
				public RandomGenerator generator(int genSize) {
					return rawGenerator(genSize, false);
				}

				@Override
				public RandomGenerator generatorWithEmbeddedEdgeCases(int genSize) {
					return rawGenerator(genSize, true);
				}

				private RandomGenerator rawGenerator(int genSize, boolean withEmbeddedEdgeCases) {
					RandomGenerator g1 = a1.generator(genSize, withEmbeddedEdgeCases);
					RandomGenerator g2 = a2.generator(genSize, withEmbeddedEdgeCases);
					RandomGenerator g3 = a3.generator(genSize, withEmbeddedEdgeCases);
					RandomGenerator g4 = a4.generator(genSize, withEmbeddedEdgeCases);
					RandomGenerator g5 = a5.generator(genSize, withEmbeddedEdgeCases);
					RandomGenerator g6 = a6.generator(genSize, withEmbeddedEdgeCases);
					RandomGenerator g7 = a7.generator(genSize, withEmbeddedEdgeCases);

					return random -> {
						List> shrinkables = asTypedList(
							g1.next(random),
							g2.next(random),
							g3.next(random),
							g4.next(random),
							g5.next(random),
							g6.next(random),
							g7.next(random)
						);
						return CombinatorsFacade.implementation.combineShrinkables(shrinkables, combineFunction(combinator));
					};
				}

				@Override
				public Optional> exhaustive(long maxNumberOfSamples) {
					return CombinatorsFacade.implementation.combineExhaustive(
						asTypedList(a1, a2, a3, a4, a5, a6, a7),
						combineFunction(combinator),
						maxNumberOfSamples
					);
				}

				@Override
				public EdgeCases edgeCases(int maxEdgeCases) {
					return CombinatorsFacade.implementation.combineEdgeCases(
						asTypedList(a1, a2, a3, a4, a5, a6, a7),
						combineFunction(combinator),
						maxEdgeCases
					);
				}
			};
		}

		public  Arbitrary flatAs(F7> flatCombinator) {
			return a1.flatMap(
				v1 -> a2.flatMap(
					v2 -> a3.flatMap(
						v3 -> a4.flatMap(
							v4 -> a5.flatMap(
								v5 -> a6.flatMap(
									v6 -> a7.flatMap(
										v7 -> flatCombinator.apply(v1, v2, v3, v4, v5, v6, v7))))))));
		}

	}

	/**
	 * Combinator for eight values.
	 */
	public static class Combinator8 {
		private final Arbitrary a1;
		private final Arbitrary a2;
		private final Arbitrary a3;
		private final Arbitrary a4;
		private final Arbitrary a5;
		private final Arbitrary a6;
		private final Arbitrary a7;
		private final Arbitrary a8;

		private Combinator8(
			Arbitrary a1, Arbitrary a2, Arbitrary a3, Arbitrary a4, Arbitrary a5, Arbitrary a6,
			Arbitrary a7, Arbitrary a8
		) {
			this.a1 = a1;
			this.a2 = a2;
			this.a3 = a3;
			this.a4 = a4;
			this.a5 = a5;
			this.a6 = a6;
			this.a7 = a7;
			this.a8 = a8;
		}

		/**
		 * Combine eight values.
		 *
		 * @param combinator function
		 * @param  return type
		 * @return arbitrary instance
		 */
		public  Arbitrary as(F8 combinator) {
			return new Arbitrary() {
				@Override
				public RandomGenerator generator(int genSize) {
					return rawGenerator(genSize, false);
				}

				@Override
				public RandomGenerator generatorWithEmbeddedEdgeCases(int genSize) {
					return rawGenerator(genSize, true);
				}

				private RandomGenerator rawGenerator(int genSize, boolean withEmbeddedEdgeCases) {
					RandomGenerator g1 = a1.generator(genSize, withEmbeddedEdgeCases);
					RandomGenerator g2 = a2.generator(genSize, withEmbeddedEdgeCases);
					RandomGenerator g3 = a3.generator(genSize, withEmbeddedEdgeCases);
					RandomGenerator g4 = a4.generator(genSize, withEmbeddedEdgeCases);
					RandomGenerator g5 = a5.generator(genSize, withEmbeddedEdgeCases);
					RandomGenerator g6 = a6.generator(genSize, withEmbeddedEdgeCases);
					RandomGenerator g7 = a7.generator(genSize, withEmbeddedEdgeCases);
					RandomGenerator g8 = a8.generator(genSize, withEmbeddedEdgeCases);

					return random -> {
						List> shrinkables = asTypedList(
							g1.next(random),
							g2.next(random),
							g3.next(random),
							g4.next(random),
							g5.next(random),
							g6.next(random),
							g7.next(random),
							g8.next(random)
						);
						return CombinatorsFacade.implementation.combineShrinkables(shrinkables, combineFunction(combinator));
					};
				}

				@Override
				public Optional> exhaustive(long maxNumberOfSamples) {
					return CombinatorsFacade.implementation.combineExhaustive(
						asTypedList(a1, a2, a3, a4, a5, a6, a7, a8),
						combineFunction(combinator),
						maxNumberOfSamples
					);
				}

				@Override
				public EdgeCases edgeCases(int maxEdgeCases) {
					return CombinatorsFacade.implementation.combineEdgeCases(
						asTypedList(a1, a2, a3, a4, a5, a6, a7, a8),
						combineFunction(combinator),
						maxEdgeCases
					);
				}
			};
		}

		public  Arbitrary flatAs(F8> flatCombinator) {
			return a1.flatMap(
				v1 -> a2.flatMap(
					v2 -> a3.flatMap(
						v3 -> a4.flatMap(
							v4 -> a5.flatMap(
								v5 -> a6.flatMap(
									v6 -> a7.flatMap(
										v7 -> a8.flatMap(
											v8 -> flatCombinator.apply(v1, v2, v3, v4, v5, v6, v7, v8)))))))));
		}
	}

	/**
	 * Combinator for any number of values.
	 */
	public static class ListCombinator {
		private final List> listOfArbitraries;

		private ListCombinator(List> listOfArbitraries) {
			this.listOfArbitraries = listOfArbitraries;
		}

		/**
		 * Combine any number of values.
		 *
		 * @param combinator function
		 * @param  return type
		 * @return arbitrary instance
		 */
		@SuppressWarnings("unchecked")
		public  Arbitrary as(Function, R> combinator) {
			return new Arbitrary() {
				@Override
				public RandomGenerator generator(int genSize) {
					return rawGenerator(genSize, false);
				}

				@Override
				public RandomGenerator generatorWithEmbeddedEdgeCases(int genSize) {
					return rawGenerator(genSize, true);
				}

				private RandomGenerator rawGenerator(int genSize, boolean withEmbeddedEdgeCases) {
					List> listOfGenerators =
							listOfArbitraries
									.stream()
									.map(a -> a.generator(genSize, withEmbeddedEdgeCases))
									.collect(Collectors.toList());

					return random -> {
						List> shrinkables =
								listOfGenerators
										.stream()
										.map(g -> g.next(random))
										.map(s -> (Shrinkable) s)
										.collect(Collectors.toList());

						Function, R> combineFunction = params -> combinator.apply((List) params);

						return CombinatorsFacade.implementation.combineShrinkables(shrinkables, combineFunction);
					};
				}

				@Override
				public Optional> exhaustive(long maxNumberOfSamples) {
					Function, R> combinedFunction = params -> combinator.apply((List) params);
					return CombinatorsFacade.implementation.combineExhaustive(
							asTypedList(listOfArbitraries.toArray()),
							combinedFunction,
							maxNumberOfSamples
					);
				}

				@Override
				public EdgeCases edgeCases(int maxEdgeCases) {
					Function, R> combinedFunction = params -> combinator.apply((List) params);
					return CombinatorsFacade.implementation.combineEdgeCases(
							asTypedList(listOfArbitraries.toArray()),
							combinedFunction,
							maxEdgeCases
					);
				}
			};
		}

		public  Arbitrary flatAs(Function, Arbitrary> flatCombinator) {
			return combineFlat(new ArrayList<>(listOfArbitraries), new ArrayList<>(), flatCombinator);
		}

		// Caution: This method is NOT tail-recursive.
		// No easy conversion to iteration possible.
		// Stack overflow possible.
		private  Arbitrary combineFlat(
			List> arbitraries,
			List values,
			Function, Arbitrary> flatCombinator
		) {
			if (arbitraries.isEmpty())
				return flatCombinator.apply(values);
			Arbitrary first = arbitraries.remove(0);
			return first.flatMap(value -> {
				values.add(0, value);
				return combineFlat(arbitraries, values, flatCombinator);
			});
		}
	}

	/**
	 * Provide access to combinator's through builder functionality.
	 * A builder is created through either {@linkplain #withBuilder(Supplier)}
	 * or {@linkplain #withBuilder(Arbitrary)}.
	 *
	 * @param  The builder's type
	 */
	@API(status = MAINTAINED, since = "1.2.0")
	public static class BuilderCombinator {
		private final Arbitrary builder;

		private BuilderCombinator(Arbitrary delegate) {
			this.builder = delegate;
		}

		public  CombinableBuilder use(Arbitrary arbitrary) {
			return new CombinableBuilder<>(builder, arbitrary);
		}

		/**
		 * Create the final arbitrary.
		 *
		 * @param buildFunction Function to map a builder to an object
		 * @param            the target object's type
		 * @return arbitrary of target object
		 */
		public  Arbitrary build(Function buildFunction) {
			return builder.map(buildFunction);
		}

		/**
		 * Create the final arbitrary if it's the builder itself.
		 *
		 * @return arbitrary of builder
		 */
		@API(status = MAINTAINED, since = "1.3.5")
		public Arbitrary build() {
			return build(Function.identity());
		}
	}

	/**
	 * Functionality to manipulate a builder. Instances are created through
	 * {@link BuilderCombinator#use(Arbitrary)}.
	 *
	 * @param  The builder's type
	 */
	@API(status = MAINTAINED, since = "1.2.0")
	public static class CombinableBuilder {
		private final Arbitrary builder;
		private final Arbitrary arbitrary;

		private CombinableBuilder(Arbitrary builder, Arbitrary arbitrary) {
			this.builder = builder;
			this.arbitrary = arbitrary;
		}

		/**
		 * Use the last provided arbitrary to change the builder object.
		 * Potentially create a different kind of builder.
		 *
		 * @param toFunction Use value provided by arbitrary to set current builder
		 *                   and return (potentially a different) builder.
		 * @param         Type of returned builder
		 * @return new {@linkplain BuilderCombinator} instance
		 */
		public  BuilderCombinator in(Combinators.F2 toFunction) {
			Arbitrary arbitraryOfC =
				Combinators.combine(arbitrary, builder)
						   .as((t, b) -> toFunction.apply(b, t));
			return new BuilderCombinator<>(arbitraryOfC);
		}

		/**
		 * Use the last provided arbitrary to change the builder object
		 * and proceed with the same builder. The most common scenario is
		 * a builder the methods of which do not return a new builder.
		 *
		 * @param setter Use value provided by arbitrary to change a builder's property.
		 * @return new {@linkplain BuilderCombinator} instance with same embedded builder
		 */
		@API(status = MAINTAINED, since = "1.4.0")
		public BuilderCombinator inSetter(BiConsumer setter) {
			F2 toFunction = (b, t) -> {
				setter.accept(b, t);
				return b;
			};
			return in(toFunction);
		}
	}

	@FunctionalInterface
	@API(status = INTERNAL)
	public interface F2 {
		R apply(T1 t1, T2 t2);
	}

	@FunctionalInterface
	@API(status = INTERNAL)
	public interface F3 {
		R apply(T1 t1, T2 t2, T3 t3);
	}

	@FunctionalInterface
	@API(status = INTERNAL)
	public interface F4 {
		R apply(T1 t1, T2 t2, T3 t3, T4 t4);
	}

	@FunctionalInterface
	@API(status = INTERNAL)
	public interface F5 {
		R apply(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5);
	}

	@FunctionalInterface
	@API(status = INTERNAL)
	public interface F6 {
		R apply(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6);
	}

	@FunctionalInterface
	@API(status = INTERNAL)
	public interface F7 {
		R apply(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7);
	}

	@FunctionalInterface
	@API(status = INTERNAL)
	public interface F8 {
		R apply(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8);
	}

}