embracing-nondeterminism-code/src/test/scala/green/thisfieldwas/embracingnondeterminism/stdlib/ListSpec.scala

90 lines
2.6 KiB
Scala

package green.thisfieldwas.embracingnondeterminism.stdlib
import green.thisfieldwas.embracingnondeterminism.data.{Monoid, MonoidLaws, SemigroupLaws}
import green.thisfieldwas.embracingnondeterminism.util._
import org.scalacheck.Arbitrary.arbitrary
import org.scalacheck.{Arbitrary, Gen}
/** Proves that Scala's List conforms to the following typeclasses:
* - Functor
* - Monad
* - Semigroup
* - Monoid
*/
class ListSpec extends Laws with SemigroupLaws with MonoidLaws {
property("List.map() preserves identity functions") {
forAll(arbitrary[List[Int]]) { fa =>
fa.map(identity) mustBe identity(fa)
}
}
property("List.map() preserves function composition") {
forAll(for {
fa <- arbitrary[List[Double]]
f <- arbitrary[Double => String]
g <- arbitrary[String => Int]
} yield (fa, f, g)) { case (fa, f, g) =>
fa.map(g compose f) mustBe fa.map(f).map(g)
}
}
property(s"List.flatMap() preserves left identity") {
forAll(for {
a <- arbitrary[Int]
h <- arbitrary[Int => List[String]]
} yield (a, h)) { case (a, h) =>
val leftIdentity = (x: Int) => List(x).flatMap(h)
leftIdentity(a) mustBe h(a)
}
}
property(s"List.flatMap() preserves right identity") {
forAll(for {
a <- arbitrary[Int]
h <- arbitrary[Int => List[String]]
} yield (a, h)) { case (a, h) =>
val rightIdentity = h(_: Int).flatMap(List(_))
rightIdentity(a) mustBe h(a)
}
}
property(s"List.flatMap() is associative") {
forAll(for {
a <- arbitrary[Double]
f <- arbitrary[Double => List[String]]
g <- arbitrary[String => List[Int]]
h <- arbitrary[Int => List[Boolean]]
} yield (a, f, g, h)) { case (a, f, g, h) =>
val assocLeft = f(_: Double).flatMap(g).flatMap(h)
val assocRight = f(_: Double).flatMap(g(_).flatMap(h))
assocLeft(a) mustBe assocRight(a)
}
}
implicit def arbitraryList[A: Arbitrary]: Arbitrary[List[A]] = Arbitrary {
for {
length <- Gen.sized(Gen.choose(0, _))
list <- Gen.listOfN(length, arbitrary[A])
} yield list
}
/** List forms a Semigroup under concatenation and a Monoid with an identity
* value of an empty List().
*
* @tparam A
* Any type held by the List.
* @return
* The Semigroup instance for List.
*/
implicit def listMonoid[A: Arbitrary]: Monoid[List[A]] = new Monoid[List[A]] {
override def empty: List[A] = List()
override def combine(left: List[A], right: List[A]): List[A] = left ++ right
}
checkSemigroupLaws[List[Int]]()
checkMonoidLaws[List[Int]]()
}