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

org.scalacheck.ops.GenFromConfig.scala Maven / Gradle / Ivy

package org.scalacheck.ops

import org.scalacheck.Gen
import org.scalacheck.Gen.RetrievalError
import org.scalacheck.rng.Seed

import scala.util.Random

final class GenFromConfig[T](
  gen: Gen[T],
  config: GenConfig,
  typeName: String
) {

  private[this] def headFromConfig(c: GenConfig): T = {
    try gen.pureApply(c.params, c.seed, c.retries)
    catch {
      case _: RetrievalError =>
        // attempts exhausted, return failure
        throw GenExceededRetryLimit(typeName, c.retries)
    }
  }

  private[this] def iteratorFromConfig(seed: Seed): Iterator[T] = {
    var s = seed
    Iterator.continually {
      val res = headFromConfig(config.withSeed(s))
      s = s.next
      res
    }
  }

  /** Get the first instance of this generator with the given configuration.
    *
    * @note this is a stateless function and calling this method with
    *       the same config will always produce the same result.
    */
  @throws[GenExceededRetryLimit](
    "When the number of attempts to generate a valid sample is exhausted because " + "the filters on this generator are too restrictive."
  )
  def head: T = headFromConfig(config)

  /** Modify the captured [[GenConfig]] using the given function.
    */
  def configured(fn: GenConfig => GenConfig): GenFromConfig[T] = new GenFromConfig(gen, fn(config), typeName)

  /** Effectively shorthand for `.configured(_.withSeed(seed)).head`.
    */
  @throws[GenExceededRetryLimit](
    "When the number of attempts to generate a valid sample is exhausted because " + "the filters on this generator are too restrictive."
  )
  def valueFor(seed: Seed): T = headFromConfig(config.withSeed(seed))

  /** An iterator made by attempting to find each next sample after the given number of retries.
    *
    * @note This uses the new pureApply feature only available in ScalaCheck versions >=1.14.x
    *
    * @return An iterator that will generate from the given starting seed
    */
  @throws[GenExceededRetryLimit](
    "When the number of attempts to generate a valid sample is exhausted because " + "the filters on this generator are too restrictive."
  )
  def iterator: Iterator[T] = iteratorFromConfig(config.seed)

  /** Get a random element from this generator using the default seed.
    *
    * Generators can run out of samples and return empty results. Typically,
    * this will be the result of bad Gen Parameters or having too many
    * suchThat() restrictions that make it difficult to find the next sample.
    */
  @throws[GenExceededRetryLimit](
    "When the number of attempts to generate a valid sample is exhausted because " + "the filters on this generator are too restrictive."
  )
  def nextRandom(rng: Random = Random): T = {
    headFromConfig(config.withSeed(config.seed.reseed(rng.nextLong())))
  }

  /** Effectively the same as calling [[nextRandom()]] with [[Iterator.continually]], except that
    * the given [[Random]] number generator is only used to generate the first seed and all subsequent
    * iterations are produced from [[Seed.next]].
    *
    * @note If you want to provide a consistent [[Seed]], you should use [[configured]]
    *       to set [[GenConfig.withSeed]] and then call [[iterator]].
    *
    * @param rng the random number generator used to initialize the seed.
    */
  @throws[GenExceededRetryLimit](
    "When the number of attempts to generate a valid sample is exhausted because " + "the filters on this generator are too restrictive."
  )
  def nextRandomIterator(rng: Random = Random): Iterator[T] = iteratorFromConfig(Seed(rng.nextLong()))

  // Keeping the following methods around to keep source compatibility with 2.6.0 and avoid some of the pain
  // of pulling in versions >= 2.7.0

  /** An iterator made by [[Iterator.continually]] calling [[Gen.sample]].
    *
    * @note If the generator has filters, then this method could return None a lot. If you want a
    *       safe way to filter the Nones, see [[iterator]]
    *
    * @note This will use a random seed for each sample. If you want to get the same iterator from
    *       the same seed, you would have to use [[iterator]] and handle the possible.
    *
    * @return An iterator of Options which are None if the generator's filters ruled out the sample.
    */
  def sampleIterator: Iterator[Option[T]] = Iterator.continually(gen.sample)

  /** Converts this generator to an [[Iterator]] in the obvious (but potentially dangerous) way.
    *
    * @note This pulls an item from the generator one at a time, however, if the generator has too
    * many restrictive filters, this can result in an infinite loop.
    *
    * @see [[iterator]] for a safer, bounded alternative.
    *
    * @return An [[Iterator]] that returns only the defined samples.
    */
  @deprecated(
    "This is generally a bad idea and is easy enough to inline. It will be removed in the next major version.",
    "2.7.0"
  )
  def toUnboundedIterator: Iterator[T] = sampleIterator.collect { case Some(x) => x }

  /** Get a random element from this generator using the default seed.
    *
    * Generators can run out of samples and return empty results. Typically,
    * this will be the result of bad Gen Parameters or having too many
    * suchThat() restrictions that make it difficult to find the next sample.
    */
  @deprecated(
    """If you need a random sample on each call, use .nextRandom()
""" + """If you want a stateless function, use .head
""" + "If you want to pass a specific Seed, use .configured(_.withSeed(seed)).head",
    "2.3.0"
  )
  @throws[GenExceededRetryLimit](
    "When the number of attempts to generate a valid sample is exhausted because " + "the filters on this generator are too restrictive."
  )
  def getOrThrow: T = headFromConfig(GenConfig.default.withSeed(Seed.random()))

  /** @see [[head]]
    */
  @deprecated(
    "Use .head instead. This method is contradictory " + "(throwing an exception makes the function partially undefined and thus impure) and verbose " + "(this has very similar semantics to the familiar .head method on Scala collections)",
    "2.7.0"
  )
  @throws[GenExceededRetryLimit](
    "When the number of attempts to generate a valid sample is exhausted because " + "the filters on this generator are too restrictive."
  )
  def getOrThrowPure: T = head
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy