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

hedgehog.core.GenT.scala Maven / Gradle / Ivy

The newest version!
package hedgehog.core

import hedgehog._
import hedgehog.predef._

/**
 * Generator for random values of `A`.
 */
case class GenT[A](run: (Size, Seed) => Tree[(Seed, Option[A])]) {

  def map[B](f: A => B): GenT[B] =
    GenT((size, seed) => run(size, seed).map(t => t.copy(_2 = t._2.map(f))))

  def flatMap[B](f: A => GenT[B]): GenT[B] =
    GenT((size, seed) => run(size, seed).flatMap(x =>
      x._2.fold(Tree.TreeApplicative.point(x.copy(_2 = Option.empty[B])))(a => f(a).run(size, x._1))
    ))

  def mapTree[B](f: Tree[(Seed, Option[A])] => Tree[(Seed, Option[B])]): GenT[B] =
    GenT((size, seed) => f(run(size, seed)))

  /**********************************************************************/
  // Shrinking

  /**
   * Apply a shrinking function to a generator.
   */
  def shrink(f: A => List[A]): GenT[A] =
    mapTree(_.expand(x =>
      x._2.fold(List.empty[(Seed, Option[A])])(a => f(a).map(y => (x._1, Some(y))))
    ))

  /**
   * Throw away a generator's shrink tree.
   */
  def prune: GenT[A] =
    mapTree(_.prune)

  /**********************************************************************/
  // Combinators - Property

  def log(name: Name): PropertyT[A] =
    // TODO Add better render, although I don't really like Show
    forAllWithLog(x => ForAll(name, x.toString))

  def forAll: PropertyT[A] =
    // TODO Add better render, although I don't really like Show
    forAllWithLog(x => x.toString)

  def forAllWithLog(f: A => Log): PropertyT[A] =
    for {
      x <- propertyT.fromGen(this)
      _ <- propertyT.writeLog(f(x))
    } yield x

  // Different from Haskell version, which uses the MonadGen typeclass
  def lift: PropertyT[A] =
    propertyT.fromGen(this)

  /**********************************************************************/
  // Combinators - Size

  /**
   * Override the size parameter. Returns a generator which uses the given size
   * instead of the runtime-size parameter.
   */
  def resize(size: Size): GenT[A] =
    if (size.value < 0)
      sys.error("Hedgehog.Random.resize: negative size")
    else
      GenT((_, seed) => run(size, seed))

  /**
   * Adjust the size parameter by transforming it with the given function.
   */
  def scale(f: Size => Size): GenT[A] =
    Gen.sized(n => resize(f(n)))

  /**
   * Make a generator smaller by scaling its size parameter.
   */
  def small: GenT[A] =
    scale(_.golden)

  /**********************************************************************/
  // Combinators - Conditional

  /**
   * Discards the generator if the generated value does not satisfy the predicate.
   */
  def ensure(p: A => Boolean): GenT[A] =
    this.flatMap(x => if (p(x)) Gen.constant(x) else Gen.discard)

  /**
   * Generates a value that satisfies a predicate.
   *
   * We keep some state to avoid looping forever.
   * If we trigger these limits then the whole generator is discarded.
   */
  def filter(p: A => Boolean): GenT[A] =
    Gen.filter(this)(p)

  /**
   * Generates a value that satisfies a predicate.
   *
   * Equivalent to `filter` and is used in for-comprehensions.
   */
  def withFilter(p: A => Boolean): GenT[A] =
    filter(p)

  /**********************************************************************/
  // Combinators - Collections

  /** Generates a 'None' some of the time. */
  def option: GenT[Option[A]] =
    Gen.sized(size =>
      Gen.frequency1(
        2 -> Gen.constant(Option.empty[A])
      , 1 + size.value -> this.map(some)
      )
    )

  /** Generates a list using a 'Range' to determine the length. */
  def list(range: Range[Int]): GenT[List[A]] =
    Gen.list(this, range)
}

abstract class GenImplicits1 {

  implicit val GenFunctor: Functor[GenT] =
    new Functor[GenT] {
      override def map[A, B](fa: GenT[A])(f: A => B): GenT[B] =
        fa.map(f)
    }
}

abstract class GenImplicits2 extends GenImplicits1 {

  implicit val GenApplicative: Applicative[GenT] =
    new Applicative[GenT] {
      def point[A](a: => A): GenT[A] =
        GenT((_, s) => Tree.TreeApplicative.point((s, Some(a))))
      override def ap[A, B](fa: => GenT[A])(f: => GenT[A => B]): GenT[B] =
        GenT((size, seed) => {
          val f2 = f.run(size, seed)
          val fa2 = fa.run(size, f2.value._1)
          Applicative.zip(fa2, f2).map { case ((seed2, oa), (_, o)) =>
            (seed2, o.flatMap(y => oa.map(y(_))))
          }
        })
    }
}

object GenT extends GenImplicits2 {

  implicit val GenMonad: Monad[GenT] =
    new Monad[GenT] {

     override def map[A, B](fa: GenT[A])(f: A => B): GenT[B] =
       fa.map(f)

     override def point[A](a: => A): GenT[A] =
       GenApplicative.point(a)

     override def ap[A, B](fa: => GenT[A])(f: => GenT[A => B]): GenT[B] =
       GenApplicative.ap(fa)(f)

      override def bind[A, B](fa: GenT[A])(f: A => GenT[B]): GenT[B] =
        fa.flatMap(f)
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy