You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

46 lines
1.7 KiB

package green.thisfieldwas.embracingnondeterminism.data
import green.thisfieldwas.embracingnondeterminism.syntax.functor._
import green.thisfieldwas.embracingnondeterminism.util._
import org.scalacheck.Arbitrary.arbitrary
import scala.reflect.runtime.universe.TypeTag
/** Include this trait to check that your context `F[_]` conforms to the Functor
* laws. Provide an implicit instance of `LiftedGen` for your context `F[_]`
* and call the `checkFunctorLaws[F]()` function within your laws spec to
* register the associated properties.
*/
trait FunctorLaws { this: Laws =>
/** Defined per Functor Laws taken from the Haskell wiki:
* [[https://wiki.haskell.org/Functor#Functor_Laws]]
*
* @param TT
* The type tag of the context
* @tparam F
* The context type being tested
*/
def checkFunctorLaws[F[_]: Functor: LiftedGen]()(implicit TT: TypeTag[F[Any]]): Unit = {
// Mapping the identity function produces the same result as applying the
// identity function to the context.
property(s"${TT.name} Functor preserves identity functions") {
forAll(arbitrary[Double].lift) { fa =>
// Haskell: fmap id = id
fa.map(identity) mustBe identity(fa)
}
}
// Mapping composed functions g after f produces the same result as
// separately mapping f and then g.
property(s"${TT.name} Functor preserves function composition") {
forAll(for {
fa <- arbitrary[Double].lift
f <- arbitrary[Double => String]
g <- arbitrary[String => Int]
} yield (fa, f, g)) { case (fa, f, g) =>
// Haskell: fmap (f . g) == fmap f . fmap g
fa.map(g compose f) mustBe fa.map(f).map(g)
}
}
}
}