Skip to content

Instantly share code, notes, and snippets.

@TrustNoOne
Last active October 13, 2016 12:33
Show Gist options
  • Save TrustNoOne/7bb9ef01732762c3a434 to your computer and use it in GitHub Desktop.
Save TrustNoOne/7bb9ef01732762c3a434 to your computer and use it in GitHub Desktop.
Shapeless Random case class/sealed trait family generator
import shapeless._
import shapeless.ops.coproduct.ToHList
import shapeless.ops.hlist.ToTraversable
@annotation.implicitNotFound(msg = "No EveryRandomObject found for ${A}. ${A} could not be a sealed hierarchy or one of its subtypes could not have a RandomObject instance available")
trait EveryRandomObject[A] {
def generate(): List[A]
}
object EveryRandomObject {
def apply[A](implicit ript: EveryRandomObject[A]): List[A] = ript.generate()
implicit def default[A, C <: Coproduct, L <: HList](
implicit
gen: Generic.Aux[A, C],
c2h: ToHList.Aux[C, L],
rnd: MkEveryRandomCoproduct[C, L],
toList: ToTraversable.Aux[L, List, A]
): EveryRandomObject[A] = new EveryRandomObject[A] {
override def generate(): List[A] = rnd.generate().toList
}
}
/**
* Typeclass to generate one random object per subtype in a closed hierarchy
*/
trait MkEveryRandomCoproduct[C <: Coproduct, L <: HList] {
def generate(): L
}
trait LowPriorityMkEveryRandomCoproduct {
implicit def cconsMkRandomCoproductSingleton[H, T <: Coproduct, LT <: HList](
implicit
witness: Witness.Aux[H],
tail: MkEveryRandomCoproduct[T, LT]
): MkEveryRandomCoproduct[H :+: T, H :: LT] =
new MkEveryRandomCoproduct[H :+: T, H :: LT] {
override def generate(): H :: LT = witness.value :: tail.generate()
}
}
object MkEveryRandomCoproduct extends LowPriorityMkEveryRandomCoproduct {
implicit def cnilMkEveryRandomCoproduct[A]: MkEveryRandomCoproduct[CNil, HNil] = new MkEveryRandomCoproduct[CNil, HNil] {
override def generate(): HNil = HNil
}
implicit def cconsMkEveryRandomCoproduct[H, T <: Coproduct, LT <: HList](
implicit
head: RandomObject[H],
tail: MkEveryRandomCoproduct[T, LT]
): MkEveryRandomCoproduct[H :+: T, H :: LT] =
new MkEveryRandomCoproduct[H :+: T, H :: LT] {
override def generate(): H :: LT = head.generate() :: tail.generate()
}
}
import shapeless._
import scala.collection.generic.CanBuild
import scala.language.higherKinds
import scala.util.Random
/**
* Typeclass for creating random test objects.
*/
trait RandomObject[A] {
def generate: A
}
object RandomObject extends RandomGenericObject {
def apply[A](implicit rnd: RandomObject[A]): A = rnd.generate
implicit val randomString: RandomObject[String] = new RandomObject[String] {
def generate = Random.nextString(5 + Random.nextInt(10))
}
implicit val randomInt: RandomObject[Int] = new RandomObject[Int] {
def generate = Random.nextInt()
}
implicit val randomLong: RandomObject[Long] = new RandomObject[Long] {
def generate = Random.nextLong()
}
implicit val randomBoolean: RandomObject[Boolean] = new RandomObject[Boolean] {
def generate = Random.nextBoolean()
}
// options and collections
implicit def randomOption[A: RandomObject]: RandomObject[Option[A]] = new RandomObject[Option[A]] {
override def generate: Option[A] = {
if (Random.nextBoolean()) Some(implicitly[RandomObject[A]].generate) else None
}
}
implicit def randomColl[A: RandomObject, C[_]](implicit canBuild: CanBuild[A, C[A]]): RandomObject[C[A]] = new RandomObject[C[A]] {
override def generate: C[A] = {
val b = canBuild()
val numberOfElems = Random.nextInt(5) + 2
b.sizeHint(numberOfElems)
(1 to numberOfElems) foreach (_ ⇒ b += implicitly[RandomObject[A]].generate)
b.result()
}
}
implicit def randomTupleColl[A: RandomObject, B: RandomObject, C[_, _]](implicit canBuild: CanBuild[(A, B), C[A, B]]): RandomObject[C[A, B]] = new RandomObject[C[A, B]] {
override def generate: C[A, B] = {
val b = canBuild()
val numberOfElems = Random.nextInt(5) + 2
b.sizeHint(numberOfElems)
(1 to numberOfElems) foreach { _ ⇒
val elem = (implicitly[RandomObject[A]].generate, implicitly[RandomObject[B]].generate)
b += elem
}
b.result()
}
}
}
trait MkRandomHList[L <: HList] {
def generate: L
}
object MkRandomHList {
implicit val hNilMkRandomHList = new MkRandomHList[HNil] {
def generate = HNil
}
implicit def hconsMkRandomHList[H, T <: HList](
implicit
head: RandomObject[H],
tail: MkRandomHList[T]
): MkRandomHList[H :: T] = new MkRandomHList[H :: T] {
def generate: H :: T = head.generate :: tail.generate
}
}
trait MkRandomCoproduct[C <: Coproduct] {
def generate: C
}
trait LowPriorityMkRandomCoproduct {
implicit def cconsMkRandomCoproductSingleton[H, T <: Coproduct, N <: Nat](implicit
witness: Witness.Aux[H],
tail: MkRandomCoproduct[T],
length: ops.coproduct.Length.Aux[T, N],
n: ops.nat.ToInt[N]
): MkRandomCoproduct[H :+: T] = new MkRandomCoproduct[H :+: T] {
def generate = if (Random.nextInt(n() + 1) == 0) Inl(witness.value) else Inr(tail.generate)
}
}
object MkRandomCoproduct extends LowPriorityMkRandomCoproduct {
implicit val cnilMkRandomCoproduct: MkRandomCoproduct[CNil] = new MkRandomCoproduct[CNil] {
override def generate: CNil = throw new IllegalStateException("CNil")
}
implicit def cconsMkRandomCoproduct[H, T <: Coproduct, N <: Nat](
implicit
head: RandomObject[H],
tail : MkRandomCoproduct[T],
length: ops.coproduct.Length.Aux[T, N],
n: ops.nat.ToInt[N]
): MkRandomCoproduct[H :+: T] = new MkRandomCoproduct[H :+: T] {
def generate: H :+: T = if (Random.nextInt(n() + 1) == 0) Inl(head.generate) else Inr(tail.generate)
}
}
trait RandomGenericObject {
implicit def fromMKRandomHList[A, L <: HList](
implicit
gen: Generic.Aux[A, L],
rnd: Lazy[MkRandomHList[L]]
): RandomObject[A] = new RandomObject[A] {
def generate: A = gen.from(rnd.value.generate)
}
implicit def fromMKRandomCoproduct[A, C <: Coproduct](
implicit
gen: Generic.Aux[A, C],
rnd: Lazy[MkRandomCoproduct[C]]
): RandomObject[A] = new RandomObject[A] {
def generate: A = gen.from(rnd.value.generate)
}
}
sealed trait X
case class A(a: Int, b: B) extends X
case class B(b: String) extends X
case class C(c: Boolean, x: X) extends X
case object D extends X
case object E extends X
object Test extends App {
println(RandomObject[Int])
println(RandomObject[String])
println(RandomObject[Boolean])
println(RandomObject[Map[String, Boolean]])
println(RandomObject[A])
println(RandomObject[Set[B]])
println(RandomObject[X])
println(RandomObject[Map[String, Option[X]]])
println(RandomObject[Map[Int, List[Option[X]]]])
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment