Skip to content

Instantly share code, notes, and snippets.

@dbousamra
Created May 30, 2017 07:51
Show Gist options
  • Save dbousamra/d47c315e3101e42be995c3e6e50b9340 to your computer and use it in GitHub Desktop.
Save dbousamra/d47c315e3101e42be995c3e6e50b9340 to your computer and use it in GitHub Desktop.
case class Gen[A](g: () => A) {
/*
* Evaluates a Gen, yielding a value
*/
def sample: A = {
g()
}
/*
* Transforms a Gen
*/
def map[B](f: A => B): Gen[B] = {
Gen.unit(f(g()))
}
/*
* Sequences a Gen with another Gen
*/
def flatMap[B](f: A => Gen[B]): Gen[B] = {
map(f).map(_.sample)
}
}
object Gen {
/*
* Lazily lift an expression into the Gen context. The monadic point/unit/pure
*/
def unit[A](a: => A): Gen[A] = {
Gen(() => a)
}
/*
* A generator that yields a char
*/
def char: Gen[Char] = {
Gen.unit(Random.nextPrintableChar())
}
/*
* A generator that yields an alpha char
*/
def alphaChar: Gen[Char] = {
Gen.chooseInt(0, 25).map(n => ('A' to 'Z').toList(n))
}
/*
* A generator that yields an alphanumeric char
*/
def alphaNumericChar: Gen[Char] = {
Gen.unit(Random.alphanumeric.head)
}
/*
* A generator that yields a list of A's given a Gen[A]
*/
def listOfN[A](n: Int, gen: Gen[A]): Gen[List[A]] = {
Gen.unit(List.fill(n)(gen.sample))
}
/*
* A generator that yields an int between min and max
*/
def chooseInt(min: Int, max: Int): Gen[Int] = {
Gen.unit(min + Random.nextInt(max - min))
}
/*
* A generator that yields a string with max length 24 (hardcoded, meh)
*/
def string: Gen[String] = {
Gen.chooseInt(1, 24).flatMap { lengthOfString =>
Gen.listOfN(lengthOfString, Gen.char).map(_.mkString)
}
}
/*
* Choose a gen randomly from a list of gens
*/
def elements[A](gs: List[Gen[A]]): Gen[A] = {
Gen.chooseInt(0, gs.length).flatMap(n => gs(n))
}
/*
* A generator of A randomly from a list of A's
*/
def oneOf[A](g: A, gs: A*): Gen[A] = {
val items = (g +: gs)
Gen.chooseInt(0, items.length).map(n => items(n))
}
/*
* Choose a Gen randomly from a list of Gens
*/
def oneOf[A](g: Gen[A], gs: Gen[A]*): Gen[A] = {
elements((g +: gs).toList)
}
}
object Example {
val genSingularArticle: Gen[String] = Gen.oneOf(
"the",
"a"
)
val genAnimals: Gen[String] = Gen.oneOf(
"vulture",
"howler",
"wallaby",
"dalmatian",
"dolphin"
)
val genVegetables: Gen[String] = Gen.oneOf(
"carrot",
"spinach",
"squash",
"pumpkin"
)
val genNoun = Gen.oneOf(
genAnimals,
genVegetables
)
val genPastTenseVerb: Gen[String] = Gen.oneOf(
"howled",
"ate",
"pooed",
"attacked",
"walked"
)
val genAdverb: Gen[String] = Gen.oneOf(
"accidentally",
"always",
"angrily",
"anxiously",
"awkwardly",
"badly"
)
val genStory: Gen[String] = for {
article <- genSingularArticle
noun <- genNoun
adverb <- genAdverb
verb <- genPastTenseVerb
} yield s"$article $noun $adverb $verb"
val genAnimalAndVegetableStory = for {
article1 <- genSingularArticle
article2 <- genSingularArticle
animal <- genAnimals
vegetable <- genVegetables
adverb <- genAdverb
} yield s"$article1 $animal $adverb ate $article2 $vegetable"
def genNStories(n: Int): Gen[List[String]] =
Gen.listOfN(n, Gen.oneOf(genStory, genAnimalAndVegetableStory))
def main(args: Array[String]): Unit = {
println(genNStories(3).sample.mkString("\n"))
// the dolphin accidentally ate the squash
// the wallaby badly ate the carrot
// a squash awkwardly attacked
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment