Last active
October 13, 2016 12:33
-
-
Save TrustNoOne/7bb9ef01732762c3a434 to your computer and use it in GitHub Desktop.
Shapeless Random case class/sealed trait family generator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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