Skip to content

Commit

Permalink
Add hedgehog module
Browse files Browse the repository at this point in the history
  • Loading branch information
yoohaemin committed Jul 5, 2023
1 parent d61c7a6 commit d342142
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 0 deletions.
12 changes: 12 additions & 0 deletions build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,16 @@ object ziotest extends PureCrossModule {

}

object hedgehog extends PureCrossModule {

override def moduleDeps = Seq(core)

override def ivyDeps = Agg(
D.hedgehog
)

}

object cats extends PureCrossModule {

override def moduleDeps = Seq(core)
Expand Down Expand Up @@ -128,6 +138,7 @@ object D {
def fetch = ivy"com.47deg::fetch::${V.fetch}"
def scalacheck = ivy"org.scalacheck::scalacheck::${V.scalacheck}"
def cats = ivy"org.typelevel::cats-core::${V.cats}"
def hedgehog = ivy"qa.hedgehog::hedgehog-core::${V.hedgehog}"

def kindProjector = ivy"org.typelevel:::kind-projector:${V.kindProjector}"
}
Expand All @@ -144,6 +155,7 @@ object V {
def fetch = "3.1.2"
def izumiReflect = "2.3.8"
def scalacheck = "1.17.0"
def hedgehog = "0.10.1"

def kindProjector = "0.13.2"
}
Expand Down
113 changes: 113 additions & 0 deletions hedgehog/src/decrel/hedgehog/gen.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Copyright (c) 2022 Haemin Yoo
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

package decrel.hedgehog

import decrel.Relation
import decrel.reify.monofunctor.*
import hedgehog.*
import hedgehog.predef.*

import scala.collection.{ mutable, IterableOps }

trait gen extends module[Gen] {

//////// Basic premises

override protected def flatMap[A, B](gen: Gen[A])(f: A => Gen[B]): Gen[B] =
gen.flatMap(f)

override protected def map[A, B](gen: Gen[A])(f: A => B): Gen[B] =
gen.map(f)

override protected def succeed[A](a: A): Gen[A] =
Gen.constant(a)

//////// Syntax for implementing relations

implicit final class GenObjectOps(private val gen: Gen.type) {

def relationSingle[Rel, In, Out](
relation: Rel & Relation.Single[In, Out]
)(
f: In => Gen[Out]
): Proof.Single[Rel & Relation.Single[In, Out], In, Out] =
new Proof.Single[Rel & Relation.Single[In, Out], In, Out] {
override val reify: ReifiedRelation[In, Out] = reifiedRelation(f)
}

def relationOptional[Rel, In, Out](
relation: Rel & Relation.Optional[In, Out]
)(
f: In => Gen[Option[Out]]
): Proof.Optional[Rel & Relation.Optional[In, Out], In, Out] =
new Proof.Optional[Rel & Relation.Optional[In, Out], In, Out] {
override val reify: ReifiedRelation[In, Option[Out]] = reifiedRelation(f)
}

def relationMany[Rel, In, Out, CC[+A] <: Iterable[A] & IterableOps[A, CC, CC[A]]](
relation: Rel & Relation.Many[In, List, Out]
)(
f: In => Gen[CC[Out]]
): Proof.Many[Rel & Relation.Many[In, CC, Out], In, CC, Out] =
new Proof.Many[Rel & Relation.Many[In, CC, Out], In, CC, Out] {
override val reify: ReifiedRelation[In, CC[Out]] = reifiedRelation(f)
}
}

private def reifiedRelation[In, Out](f: In => Gen[Out]): ReifiedRelation[In, Out] =
new ReifiedRelation.Custom[In, Out] {
override def apply(in: In): Gen[Out] =
applyMultiple(List(in)).map(_.head)

override def applyMultiple[Coll[+A] <: Iterable[A] & IterableOps[A, Coll, Coll[A]]](
ins: Coll[In]
): Gen[Coll[Out]] = {
val F = implicitly[Applicative[Gen]] // Monad for Gen wasn't stack safe last time I checked

def addElem[A](
listGen: Gen[mutable.Builder[A, Coll[A]]],
aGen: Gen[A]
): Gen[mutable.Builder[A, Coll[A]]] =
F.ap(listGen)(F.map(aGen)(a => _.addOne(a)))

val ins_ : IterableOps[In, Coll, Coll[In]] = ins
val factory = ins_.iterableFactory
val iterator = ins_.iterator
val builder: mutable.Builder[Out, Coll[Out]] = factory.newBuilder[Out]

if (iterator.hasNext) {
val head = f(iterator.next())
var builderGen: Gen[mutable.Builder[Out, Coll[Out]]] =
head.map(builder.addOne)

while (iterator.hasNext) {
val in = iterator.next()
val out: Gen[Out] = f(in)
builderGen = addElem(builderGen, out)
}

builderGen.map(_.result())
} else {
Gen.constant(factory.empty[Out])
}
}
}

//////// Syntax for using relations in tests

implicit final class GenOps[A](private val gen: Gen[A]) {

def expand[Rel, B](rel: Rel & Relation[A, B])(implicit
proof: Proof[Rel & Relation[A, B], A, B]
): Gen[B] = gen.flatMap(rel.reify(proof).apply)

}
}

object gen extends gen

0 comments on commit d342142

Please sign in to comment.