#### 81 lines 3.3 KiB Scala Raw Permalink Blame History

 ```package green.thisfieldwas.embracingnondeterminism.control ``` ``` ``` ```import green.thisfieldwas.embracingnondeterminism.util._ ``` ```import org.scalacheck.Arbitrary ``` ```import org.scalacheck.Arbitrary.arbitrary ``` ``` ``` ```import scala.reflect.runtime.universe.TypeTag ``` ``` ``` ```/** Include this trait to check that your context `F[_]` conforms to the Monad ``` ``` * laws. Provide an implicit instance of `LiftedGen` for your context `F[_]` ``` ``` * and call the `checkMonadLaws[F]()` function within your laws spec to ``` ``` * register the associated properties. ``` ``` */ ``` ```trait MonadLaws { this: Laws with ApplicativeLaws => ``` ``` ``` ``` import green.thisfieldwas.embracingnondeterminism.syntax.applicative._ ``` ``` import green.thisfieldwas.embracingnondeterminism.syntax.monad._ ``` ``` ``` ``` implicit def arbitraryFA[F[_]: LiftedGen, A: Arbitrary]: Arbitrary[F[A]] = Arbitrary(arbitrary[A].lift) ``` ``` ``` ``` /** Defined per Monad laws taken from the Haskell wiki: ``` ``` * [[https://wiki.haskell.org/Monad_laws#The_three_laws]] ``` ``` * ``` ``` * These laws extend the Applicative laws, so `checkApplicativeLaws[F]()` ``` ``` * should be executed alongside this function. ``` ``` * ``` ``` * The laws as defined here leverage Kleisli composition, which is defined ``` ``` * using the operator `>=>` in terms of `flatMap()`, to better highlight the ``` ``` * left and right identities and associativity that should be exhibited by ``` ``` * the composition of monadic operations. ``` ``` * ``` ``` * @param TT ``` ``` * The type tag of the context ``` ``` * @tparam F ``` ``` * The context type being tested ``` ``` */ ``` ``` def checkMonadLaws[F[_]: Monad: LiftedGen]()(implicit TT: TypeTag[F[Any]]): Unit = { ``` ``` property(s"\${TT.name} Monad composition preserves left identity") { ``` ``` // The identity of Kleisli composition simply lifts a value into the ``` ``` // context. Kleisli composition of a function after `pure()` when applied ``` ``` // to a value will produce the same result as applying the function ``` ``` // directly to the value. ``` ``` forAll(for { ``` ``` a <- arbitrary[Int] ``` ``` h <- arbitrary[Int => F[String]] ``` ``` } yield (a, h)) { case (a, h) => ``` ``` val leftIdentity = ((_: Int).pure[F]) >=> h ``` ``` leftIdentity(a) mustBe h(a) ``` ``` } ``` ``` } ``` ``` property(s"\${TT.name} Monad composition preserves right identity") { ``` ``` // The identity of Kleisli composition simply lifts a value into the ``` ``` // context. Kleisli composition of `pure()` after a function when applied ``` ``` // to a value will produce the same result as applying the function ``` ``` // directly to the value. ``` ``` forAll(for { ``` ``` a <- arbitrary[Int] ``` ``` h <- arbitrary[Int => F[String]] ``` ``` } yield (a, h)) { case (a, h) => ``` ``` val rightIdentity = h >=> ((_: String).pure[F]) ``` ``` rightIdentity(a) mustBe h(a) ``` ``` } ``` ``` } ``` ``` property(s"\${TT.name} Monad composition is associative") { ``` ``` // `flatMap()` and thus Kleisli composition are both associative. ``` ``` // This means that your program may be factored with these operations ``` ``` // in any arbitrary grouping and the output will be the same. ``` ``` forAll(for { ``` ``` a <- arbitrary[Double] ``` ``` f <- arbitrary[Double => F[String]] ``` ``` g <- arbitrary[String => F[Int]] ``` ``` h <- arbitrary[Int => F[Boolean]] ``` ``` } yield (a, f, g, h)) { case (a, f, g, h) => ``` ``` val assocLeft = (f >=> g) >=> h ``` ``` val assocRight = f >=> (g >=> h) ``` ``` assocLeft(a) mustBe assocRight(a) ``` ``` } ``` ``` } ``` ``` } ``` ```} ```