diff --git a/core/src/main/scala/cats/Reducible.scala b/core/src/main/scala/cats/Reducible.scala index 10dc1b2b7e..b993afd808 100644 --- a/core/src/main/scala/cats/Reducible.scala +++ b/core/src/main/scala/cats/Reducible.scala @@ -1,5 +1,7 @@ package cats +import cats.data.NonEmptyList + import simulacrum.typeclass /** @@ -113,6 +115,11 @@ import simulacrum.typeclass def sequence1_[G[_], A](fga: F[G[A]])(implicit G: Apply[G]): G[Unit] = G.map(reduceLeft(fga)((x, y) => G.map2(x, y)((_, b) => b)))(_ => ()) + def toNonEmptyList[A](fa: F[A]): NonEmptyList[A] = + reduceRightTo(fa)(a => NonEmptyList(a, Nil)) { (a, lnel) => + lnel.map { case NonEmptyList(h, t) => NonEmptyList(a, h :: t) } + }.value + def compose[G[_]: Reducible]: Reducible[λ[α => F[G[α]]]] = new ComposedReducible[F, G] { val F = self diff --git a/core/src/main/scala/cats/Traverse.scala b/core/src/main/scala/cats/Traverse.scala index 59aefe37b2..c041089077 100644 --- a/core/src/main/scala/cats/Traverse.scala +++ b/core/src/main/scala/cats/Traverse.scala @@ -96,9 +96,9 @@ import simulacrum.typeclass * scala> import cats.implicits._ * scala> val x: List[ValidatedNel[String, Int]] = List(Validated.valid(1), Validated.invalid("a"), Validated.invalid("b")).map(_.toValidatedNel) * scala> x.sequenceU - * res0: cats.data.ValidatedNel[String,List[Int]] = Invalid(OneAnd(a,List(b))) + * res0: cats.data.ValidatedNel[String,List[Int]] = Invalid(NonEmptyList(a, b)) * scala> x.sequence[ValidatedNel[String, ?], Int] - * res1: cats.data.ValidatedNel[String,List[Int]] = Invalid(OneAnd(a,List(b))) + * res1: cats.data.ValidatedNel[String,List[Int]] = Invalid(NonEmptyList(a, b)) * }}} */ def sequenceU[GA](fga: F[GA])(implicit U: Unapply[Applicative, GA]): U.M[F[U.A]] = diff --git a/core/src/main/scala/cats/data/NonEmptyList.scala b/core/src/main/scala/cats/data/NonEmptyList.scala new file mode 100644 index 0000000000..cebb6a7c27 --- /dev/null +++ b/core/src/main/scala/cats/data/NonEmptyList.scala @@ -0,0 +1,255 @@ +package cats +package data + +import cats.instances.list._ +import cats.syntax.order._ + +import scala.annotation.tailrec +import scala.collection.mutable.ListBuffer + +/** + * A data type which represents a non empty list of A, with + * single element (head) and optional structure (tail). + */ +final case class NonEmptyList[A](head: A, tail: List[A]) { + + /** + * Return the head and tail into a single list + */ + def toList: List[A] = head :: tail + + /** + * Applies f to all the elements of the structure + */ + def map[B](f: A => B): NonEmptyList[B] = + NonEmptyList(f(head), tail.map(f)) + + def ++(l: List[A]): NonEmptyList[A] = + NonEmptyList(head, tail ++ l) + + def flatMap[B](f: A => NonEmptyList[B]): NonEmptyList[B] = + f(head) ++ tail.flatMap(f andThen (_.toList)) + + def ::(a: A): NonEmptyList[A] = NonEmptyList(a, head :: tail) + + /** + * remove elements not matching the predicate + */ + def filter(p: A => Boolean): List[A] = { + val ftail = tail.filter(p) + if (p(head)) head :: ftail + else ftail + } + + /** + * Append another NonEmptyList + */ + def concat(other: NonEmptyList[A]): NonEmptyList[A] = + NonEmptyList(head, tail ::: other.toList) + + /** + * Find the first element matching the predicate, if one exists + */ + def find(p: A => Boolean): Option[A] = + if (p(head)) Some(head) + else tail.find(p) + + /** + * Check whether at least one element satisfies the predicate + */ + def exists(p: A => Boolean): Boolean = + p(head) || tail.exists(p) + + /** + * Check whether all elements satisfy the predicate + */ + def forall(p: A => Boolean): Boolean = + p(head) && tail.forall(p) + + /** + * Left-associative fold on the structure using f. + */ + def foldLeft[B](b: B)(f: (B, A) => B): B = + tail.foldLeft(f(b, head))(f) + + /** + * Right-associative fold on the structure using f. + */ + def foldRight[B](lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = + Foldable[List].foldRight(toList, lb)(f) + + /** + * Left-associative reduce using f. + */ + def reduceLeft(f: (A, A) => A): A = + tail.foldLeft(head)(f) + + def traverse[G[_], B](f: A => G[B])(implicit G: Applicative[G]): G[NonEmptyList[B]] = + G.map2Eval(f(head), Always(Traverse[List].traverse(tail)(f)))(NonEmptyList(_, _)).value + + def coflatMap[B](f: NonEmptyList[A] => B): NonEmptyList[B] = { + val buf = ListBuffer.empty[B] + @tailrec def consume(as: List[A]): List[B] = + as match { + case Nil => buf.toList + case a :: as => + buf += f(NonEmptyList(a, as)) + consume(as) + } + NonEmptyList(f(this), consume(tail)) + } + + def ===(o: NonEmptyList[A])(implicit A: Eq[A]): Boolean = + (this.head === o.head) && this.tail === o.tail + + def show(implicit A: Show[A]): String = + toList.iterator.map(A.show).mkString("NonEmptyList(", ", ", ")") + + override def toString: String = s"NonEmpty$toList" +} + +object NonEmptyList extends NonEmptyListInstances { + def apply[A](head: A, tail: A*): NonEmptyList[A] = NonEmptyList(head, tail.toList) + + /** + * Create a `NonEmptyList` from a `List`. + * + * The result will be `None` if the input list is empty and `Some` wrapping a + * `NonEmptyList` otherwise. + * + * @see [[fromListUnsafe]] for an unsafe version that throws an exception if + * the input list is empty. + */ + def fromList[A](l: List[A]): Option[NonEmptyList[A]] = + l match { + case Nil => None + case h :: t => Some(NonEmptyList(h, t)) + } + + /** + * Create a `NonEmptyList` from a `List`, or throw an + * `IllegalArgumentException` if the input list is empty. + * + * @see [[fromList]] for a safe version that returns `None` if the input list + * is empty. + */ + def fromListUnsafe[A](l: List[A]): NonEmptyList[A] = + l match { + case Nil => throw new IllegalArgumentException("Cannot create NonEmptyList from empty list") + case h :: t => NonEmptyList(h, t) + } + + def fromReducible[F[_], A](fa: F[A])(implicit F: Reducible[F]): NonEmptyList[A] = + F.toNonEmptyList(fa) +} + +private[data] sealed trait NonEmptyListInstances extends NonEmptyListInstances0 { + + implicit val catsDataInstancesForNonEmptyList: SemigroupK[NonEmptyList] with Reducible[NonEmptyList] + with Comonad[NonEmptyList] with Traverse[NonEmptyList] with MonadRec[NonEmptyList] = + new NonEmptyReducible[NonEmptyList, List] with SemigroupK[NonEmptyList] + with Comonad[NonEmptyList] with Traverse[NonEmptyList] with MonadRec[NonEmptyList] { + + def combineK[A](a: NonEmptyList[A], b: NonEmptyList[A]): NonEmptyList[A] = + a concat b + + override def split[A](fa: NonEmptyList[A]): (A, List[A]) = (fa.head, fa.tail) + + override def reduceLeft[A](fa: NonEmptyList[A])(f: (A, A) => A): A = + fa.reduceLeft(f) + + override def map[A, B](fa: NonEmptyList[A])(f: A => B): NonEmptyList[B] = + fa map f + + def pure[A](x: A): NonEmptyList[A] = + NonEmptyList(x, List.empty) + + def flatMap[A, B](fa: NonEmptyList[A])(f: A => NonEmptyList[B]): NonEmptyList[B] = + fa flatMap f + + def coflatMap[A, B](fa: NonEmptyList[A])(f: NonEmptyList[A] => B): NonEmptyList[B] = + fa coflatMap f + + def extract[A](fa: NonEmptyList[A]): A = fa.head + + def traverse[G[_], A, B](fa: NonEmptyList[A])(f: A => G[B])(implicit G: Applicative[G]): G[NonEmptyList[B]] = + fa traverse f + + override def foldLeft[A, B](fa: NonEmptyList[A], b: B)(f: (B, A) => B): B = + fa.foldLeft(b)(f) + + override def foldRight[A, B](fa: NonEmptyList[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = + fa.foldRight(lb)(f) + + def tailRecM[A, B](a: A)(f: A => NonEmptyList[A Xor B]): NonEmptyList[B] = { + val buf = new ListBuffer[B] + @tailrec def go(v: NonEmptyList[A Xor B]): Unit = v.head match { + case Xor.Right(b) => + buf += b + NonEmptyList.fromList(v.tail) match { + case Some(t) => go(t) + case None => () + } + case Xor.Left(a) => go(f(a) ++ v.tail) + } + go(f(a)) + NonEmptyList.fromListUnsafe(buf.result()) + } + + override def forall[A](fa: NonEmptyList[A])(p: A => Boolean): Boolean = + fa forall p + + override def exists[A](fa: NonEmptyList[A])(p: A => Boolean): Boolean = + fa exists p + + override def toList[A](fa: NonEmptyList[A]): List[A] = fa.toList + + override def toNonEmptyList[A](fa: NonEmptyList[A]): NonEmptyList[A] = fa + } + + implicit def catsDataShowForNonEmptyList[A](implicit A: Show[A]): Show[NonEmptyList[A]] = + Show.show[NonEmptyList[A]](_.show) + + implicit def catsDataSemigroupForNonEmptyList[A]: Semigroup[NonEmptyList[A]] = + SemigroupK[NonEmptyList].algebra[A] + + implicit def catsDataOrderForNonEmptyList[A](implicit A: Order[A]): Order[NonEmptyList[A]] = + new NonEmptyListOrder[A] { + val A0 = A + } +} + +private[data] sealed trait NonEmptyListInstances0 extends NonEmptyListInstances1 { + implicit def catsDataPartialOrderForNonEmptyList[A](implicit A: PartialOrder[A]): PartialOrder[NonEmptyList[A]] = + new NonEmptyListPartialOrder[A] { + val A0 = A + } +} + +private[data] sealed trait NonEmptyListInstances1 { + + implicit def catsDataEqForNonEmptyList[A](implicit A: Eq[A]): Eq[NonEmptyList[A]] = + new NonEmptyListEq[A] { + val A0 = A + } +} + +private[data] sealed trait NonEmptyListEq[A] extends Eq[NonEmptyList[A]] { + implicit def A0: Eq[A] + + override def eqv(x: NonEmptyList[A], y: NonEmptyList[A]): Boolean = x === y +} + +private[data] sealed trait NonEmptyListPartialOrder[A] extends PartialOrder[NonEmptyList[A]] with NonEmptyListEq[A] { + override implicit def A0: PartialOrder[A] + + override def partialCompare(x: NonEmptyList[A], y: NonEmptyList[A]): Double = + x.toList partialCompare y.toList +} + +private[data] sealed abstract class NonEmptyListOrder[A] extends Order[NonEmptyList[A]] with NonEmptyListPartialOrder[A] { + override implicit def A0: Order[A] + + override def compare(x: NonEmptyList[A], y: NonEmptyList[A]): Int = + x.toList compare y.toList +} diff --git a/core/src/main/scala/cats/data/OneAnd.scala b/core/src/main/scala/cats/data/OneAnd.scala index d896847f15..ae42654e4e 100644 --- a/core/src/main/scala/cats/data/OneAnd.scala +++ b/core/src/main/scala/cats/data/OneAnd.scala @@ -2,8 +2,8 @@ package cats package data import scala.annotation.tailrec -import scala.collection.mutable.ListBuffer -import cats.instances.list._ +import scala.collection.mutable.Builder +import cats.instances.stream._ /** * A data type which represents a single element (head) and some other @@ -140,21 +140,22 @@ private[data] sealed trait OneAndInstances extends OneAndLowPriority2 { } private[data] trait OneAndLowPriority0 { - implicit val catsDataComonadForOneAnd: Comonad[OneAnd[List, ?]] = - new Comonad[OneAnd[List, ?]] { - def coflatMap[A, B](fa: OneAnd[List, A])(f: OneAnd[List, A] => B): OneAnd[List, B] = { - @tailrec def consume(as: List[A], buf: ListBuffer[B]): List[B] = - as match { - case Nil => buf.toList - case a :: as => consume(as, buf += f(OneAnd(a, as))) + implicit val catsDataComonadForNonEmptyStream: Comonad[OneAnd[Stream, ?]] = + new Comonad[OneAnd[Stream, ?]] { + def coflatMap[A, B](fa: OneAnd[Stream, A])(f: OneAnd[Stream, A] => B): OneAnd[Stream, B] = { + @tailrec def consume(as: Stream[A], buf: Builder[B, Stream[B]]): Stream[B] = + if (as.isEmpty) buf.result + else { + val tail = as.tail + consume(tail, buf += f(OneAnd(as.head, tail))) } - OneAnd(f(fa), consume(fa.tail, ListBuffer.empty)) + OneAnd(f(fa), consume(fa.tail, Stream.newBuilder)) } - def extract[A](fa: OneAnd[List, A]): A = + def extract[A](fa: OneAnd[Stream, A]): A = fa.head - def map[A, B](fa: OneAnd[List, A])(f: A => B): OneAnd[List, B] = + def map[A, B](fa: OneAnd[Stream, A])(f: A => B): OneAnd[Stream, B] = fa map f } } diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index 57cbadb09e..17c1653dc7 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -173,7 +173,7 @@ final case class XorT[F[_], A, B](value: F[A Xor B]) { * scala> val v2: Validated[NonEmptyList[Error], Int] = Validated.Invalid(NonEmptyList("error 2")) * scala> val xort: XorT[Option, Error, Int] = XorT(Some(Xor.left("error 3"))) * scala> xort.withValidated { v3 => (v1 |@| v2 |@| v3.leftMap(NonEmptyList(_))).map{ case (i, j, k) => i + j + k } } - * res0: XorT[Option, NonEmptyList[Error], Int] = XorT(Some(Left(OneAnd(error 1,List(error 2, error 3))))) + * res0: XorT[Option, NonEmptyList[Error], Int] = XorT(Some(Left(NonEmptyList(error 1, error 2, error 3)))) * }}} */ def withValidated[AA, BB](f: Validated[A, B] => Validated[AA, BB])(implicit F: Functor[F]): XorT[F, AA, BB] = diff --git a/core/src/main/scala/cats/data/package.scala b/core/src/main/scala/cats/data/package.scala index f2b54b3368..e66bf5d6c9 100644 --- a/core/src/main/scala/cats/data/package.scala +++ b/core/src/main/scala/cats/data/package.scala @@ -1,33 +1,14 @@ package cats package object data { - type NonEmptyList[A] = OneAnd[List, A] type NonEmptyStream[A] = OneAnd[Stream, A] type ValidatedNel[E, A] = Validated[NonEmptyList[E], A] - def NonEmptyList[A](head: A, tail: List[A] = Nil): NonEmptyList[A] = - OneAnd(head, tail) - def NonEmptyList[A](head: A, tail: A*): NonEmptyList[A] = - OneAnd[List, A](head, tail.toList) - def NonEmptyStream[A](head: A, tail: Stream[A] = Stream.empty): NonEmptyStream[A] = OneAnd(head, tail) def NonEmptyStream[A](head: A, tail: A*): NonEmptyStream[A] = OneAnd(head, tail.toStream) - object NonEmptyList { - def fromReducible[F[_], A](fa: F[A])(implicit F: Reducible[F]): Eval[NonEmptyList[A]] = - F.reduceRightTo(fa)(a => NonEmptyList(a, Nil)) { (a, lnel) => - lnel.map { case OneAnd(h, t) => OneAnd(a, h :: t) } - } - - def fromList[A](la: List[A]): Option[NonEmptyList[A]] = - la match { - case (h :: t) => Some(OneAnd(h, t)) - case Nil => None - } - } - type ReaderT[F[_], A, B] = Kleisli[F, A, B] val ReaderT = Kleisli diff --git a/core/src/main/scala/cats/syntax/option.scala b/core/src/main/scala/cats/syntax/option.scala index 5d567c7365..9e9d0be3be 100644 --- a/core/src/main/scala/cats/syntax/option.scala +++ b/core/src/main/scala/cats/syntax/option.scala @@ -103,7 +103,7 @@ final class OptionOps[A](val oa: Option[A]) extends AnyVal { * * scala> val error1: Option[String] = Some("error!") * scala> error1.toInvalidNel(3) - * res0: ValidatedNel[String, Int] = Invalid(OneAnd(error!,List())) + * res0: ValidatedNel[String, Int] = Invalid(NonEmptyList(error!)) * * scala> val error2: Option[String] = None * scala> error2.toInvalidNel(3) @@ -149,7 +149,7 @@ final class OptionOps[A](val oa: Option[A]) extends AnyVal { * * scala> val result2: Option[Int] = None * scala> result2.toValidNel("error!") - * res1: ValidatedNel[String, Int] = Invalid(OneAnd(error!,List())) + * res1: ValidatedNel[String, Int] = Invalid(NonEmptyList(error!)) * }}} */ def toValidNel[B](b: => B): ValidatedNel[B, A] = oa.fold[ValidatedNel[B, A]](Validated.invalidNel(b))(Validated.Valid(_)) diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index 120331d922..344ce42f1b 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -15,7 +15,13 @@ object arbitrary extends ArbitraryInstances0 { // in the context of Int => Boolean. Once scalacheck supports better Function1 arbitrary // instances this can be removed. implicit def catsLawsArbitraryForIntToBool: Arbitrary[(Int) => Boolean] = - Arbitrary(getArbitrary[Int].map(x => (input) => input < x)) + Arbitrary( + getArbitrary[Int].map(x => + new Function1[Int, Boolean] { + def apply(i: Int): Boolean = i < x + + override def toString = s"" + })) implicit def catsLawsArbitraryForConst[A, B](implicit A: Arbitrary[A]): Arbitrary[Const[A, B]] = Arbitrary(A.arbitrary.map(Const[A, B])) @@ -26,6 +32,9 @@ object arbitrary extends ArbitraryInstances0 { implicit def catsLawsArbitraryForNonEmptyVector[A](implicit A: Arbitrary[A]): Arbitrary[NonEmptyVector[A]] = Arbitrary(implicitly[Arbitrary[Vector[A]]].arbitrary.flatMap(fa => A.arbitrary.map(a => NonEmptyVector(a, fa)))) + implicit def catsLawsArbitraryForNonEmptyList[A](implicit A: Arbitrary[A]): Arbitrary[NonEmptyList[A]] = + Arbitrary(implicitly[Arbitrary[List[A]]].arbitrary.flatMap(fa => A.arbitrary.map(a => NonEmptyList(a, fa)))) + implicit def catsLawsArbitraryForXor[A, B](implicit A: Arbitrary[A], B: Arbitrary[B]): Arbitrary[A Xor B] = Arbitrary(Gen.oneOf(A.arbitrary.map(Xor.left), B.arbitrary.map(Xor.right))) diff --git a/tests/src/test/scala/cats/tests/CokleisliTests.scala b/tests/src/test/scala/cats/tests/CokleisliTests.scala index 16759bd508..d7fbcf1579 100644 --- a/tests/src/test/scala/cats/tests/CokleisliTests.scala +++ b/tests/src/test/scala/cats/tests/CokleisliTests.scala @@ -39,12 +39,6 @@ class CokleisliTests extends SlowCatsSuite { // Ceremony to help scalac to do the right thing, see also #267. type CokleisliNEL[A, B] = Cokleisli[NonEmptyList, A, B] - implicit def ev0[A: Arbitrary, B: Arbitrary]: Arbitrary[CokleisliNEL[A, B]] = - catsLawsArbitraryForCokleisli - - implicit def ev1[A: Arbitrary, B: Eq]: Eq[CokleisliNEL[A, B]] = - cokleisliEq[NonEmptyList, A, B](catsLawsArbitraryForOneAnd, Eq[B]) - checkAll("Cokleisli[NonEmptyList, Int, Int]", ArrowTests[CokleisliNEL].arrow[Int, Int, Int, Int, Int, Int]) checkAll("Arrow[Cokleisli[NonEmptyList, ?, ?]]", SerializableTests.serializable(Arrow[CokleisliNEL])) } @@ -53,12 +47,6 @@ class CokleisliTests extends SlowCatsSuite { // More ceremony, see above type CokleisliNELE[A] = Cokleisli[NonEmptyList, A, A] - implicit def ev0[A: Arbitrary]: Arbitrary[CokleisliNELE[A]] = - catsLawsArbitraryForCokleisli[NonEmptyList, A, A] - - implicit def ev1[A: Eq](implicit arb: Arbitrary[A]): Eq[CokleisliNELE[A]] = - cokleisliEqE[NonEmptyList, A](catsLawsArbitraryForOneAnd, Eq[A]) - { implicit val cokleisliMonoidK = Cokleisli.catsDataMonoidKForCokleisli[NonEmptyList] checkAll("Cokleisli[NonEmptyList, Int, Int]", MonoidKTests[CokleisliNELE].monoidK[Int]) diff --git a/tests/src/test/scala/cats/tests/ConstTests.scala b/tests/src/test/scala/cats/tests/ConstTests.scala index ae0f0c0017..cab16a5e2f 100644 --- a/tests/src/test/scala/cats/tests/ConstTests.scala +++ b/tests/src/test/scala/cats/tests/ConstTests.scala @@ -6,7 +6,7 @@ import cats.kernel.laws.{GroupLaws, OrderLaws} import cats.data.{Const, NonEmptyList} import cats.functor.Contravariant import cats.laws.discipline._ -import cats.laws.discipline.arbitrary.{catsLawsArbitraryForConst, catsLawsArbitraryForOneAnd} +import cats.laws.discipline.arbitrary.{catsLawsArbitraryForConst, catsLawsArbitraryForNonEmptyList} class ConstTests extends CatsSuite { diff --git a/tests/src/test/scala/cats/tests/FoldableTests.scala b/tests/src/test/scala/cats/tests/FoldableTests.scala index 10841643a8..4287d0f2fb 100644 --- a/tests/src/test/scala/cats/tests/FoldableTests.scala +++ b/tests/src/test/scala/cats/tests/FoldableTests.scala @@ -52,9 +52,9 @@ abstract class FoldableCheck[F[_]: Foldable](name: String)(implicit ArbFInt: Arb val list = fa.toList val nelOpt = list.toNel maxOpt should === (nelOpt.map(_.maximum)) - maxOpt should === (nelOpt.map(_.unwrap.max)) + maxOpt should === (nelOpt.map(_.toList.max)) minOpt should === (nelOpt.map(_.minimum)) - minOpt should === (nelOpt.map(_.unwrap.min)) + minOpt should === (nelOpt.map(_.toList.min)) maxOpt.forall(i => fa.forall(_ <= i)) should === (true) minOpt.forall(i => fa.forall(_ >= i)) should === (true) } diff --git a/tests/src/test/scala/cats/tests/ListTests.scala b/tests/src/test/scala/cats/tests/ListTests.scala index fc92265ce8..052c752384 100644 --- a/tests/src/test/scala/cats/tests/ListTests.scala +++ b/tests/src/test/scala/cats/tests/ListTests.scala @@ -21,7 +21,7 @@ class ListTests extends CatsSuite { test("nel => list => nel returns original nel")( forAll { fa: NonEmptyList[Int] => - fa.unwrap.toNel should === (Some(fa)) + fa.toList.toNel should === (Some(fa)) } ) diff --git a/tests/src/test/scala/cats/tests/NestedTests.scala b/tests/src/test/scala/cats/tests/NestedTests.scala index d2318b0860..e4417ccb96 100644 --- a/tests/src/test/scala/cats/tests/NestedTests.scala +++ b/tests/src/test/scala/cats/tests/NestedTests.scala @@ -92,17 +92,8 @@ class NestedTests extends CatsSuite { checkAll("Foldable[Nested[List, ListWrapper, ?]]", SerializableTests.serializable(Foldable[Nested[List, ListWrapper, ?]])) } - { - // SI-2712? It can resolve Reducible[NonEmptyList] and Reducible[NonEmptyVector] but not - // Reducible[Nested[NonEmptyList, NonEmptyVector, ?]] - // Similarly for Arbitrary. - implicit val reducible = Nested.catsDataReducibleForNested[NonEmptyList, NonEmptyVector] - implicit val arbitrary0 = catsLawsArbitraryForNested[NonEmptyList, NonEmptyVector, Int] - implicit val arbitrary1 = catsLawsArbitraryForNested[NonEmptyList, NonEmptyVector, Option[Int]] - - checkAll("Nested[NonEmptyList, NonEmptyVector, ?]", ReducibleTests[Nested[NonEmptyList, NonEmptyVector, ?]].reducible[Option, Int, Int]) - checkAll("Reducible[Nested[NonEmptyList, NonEmptyVector, ?]]", SerializableTests.serializable(reducible)) - } + checkAll("Nested[NonEmptyList, NonEmptyVector, ?]", ReducibleTests[Nested[NonEmptyList, NonEmptyVector, ?]].reducible[Option, Int, Int]) + checkAll("Reducible[Nested[NonEmptyList, NonEmptyVector, ?]]", SerializableTests.serializable(Reducible[Nested[NonEmptyList, NonEmptyVector, ?]])) { // SemigroupK composition diff --git a/tests/src/test/scala/cats/tests/NonEmptyListTests.scala b/tests/src/test/scala/cats/tests/NonEmptyListTests.scala new file mode 100644 index 0000000000..a12bd35ec1 --- /dev/null +++ b/tests/src/test/scala/cats/tests/NonEmptyListTests.scala @@ -0,0 +1,193 @@ +package cats +package tests + +import cats.kernel.laws.{GroupLaws, OrderLaws} + +import cats.data.NonEmptyList +import cats.laws.discipline.{ComonadTests, SemigroupKTests, MonadRecTests, SerializableTests, TraverseTests, ReducibleTests} +import cats.laws.discipline.arbitrary._ + +class NonEmptyListTests extends CatsSuite { + // Lots of collections here.. telling ScalaCheck to calm down a bit + implicit override val generatorDrivenConfig: PropertyCheckConfiguration = + PropertyCheckConfig(maxSize = 5, minSuccessful = 20) + + checkAll("NonEmptyList[Int]", OrderLaws[NonEmptyList[Int]].order) + + checkAll("NonEmptyList[Int] with Option", TraverseTests[NonEmptyList].traverse[Int, Int, Int, Int, Option, Option]) + checkAll("Traverse[NonEmptyList[A]]", SerializableTests.serializable(Traverse[NonEmptyList])) + + checkAll("NonEmptyList[Int]", ReducibleTests[NonEmptyList].reducible[Option, Int, Int]) + checkAll("Reducible[NonEmptyList]", SerializableTests.serializable(Reducible[NonEmptyList])) + + checkAll("NonEmptyList[Int]", MonadRecTests[NonEmptyList].monadRec[Int, Int, Int]) + checkAll("Monad[NonEmptyList[A]]", SerializableTests.serializable(Monad[NonEmptyList])) + + checkAll("NonEmptyList[Int]", SemigroupKTests[NonEmptyList].semigroupK[Int]) + checkAll("SemigroupK[NonEmptyList[A]]", SerializableTests.serializable(SemigroupK[NonEmptyList])) + + checkAll("NonEmptyList[Int]", GroupLaws[NonEmptyList[Int]].semigroup) + checkAll("Semigroup[NonEmptyList[Int]]", SerializableTests.serializable(Semigroup[NonEmptyList[Int]])) + + checkAll("NonEmptyList[Int]", ComonadTests[NonEmptyList].comonad[Int, Int, Int]) + checkAll("Comonad[NonEmptyList]", SerializableTests.serializable(Comonad[NonEmptyList])) + + checkAll("NonEmptyList[ListWrapper[Int]]", OrderLaws[NonEmptyList[ListWrapper[Int]]].eqv) + checkAll("Eq[NonEmptyList[ListWrapper[Int]]]", SerializableTests.serializable(Eq[NonEmptyList[ListWrapper[Int]]])) + + { + implicit val A = ListWrapper.partialOrder[Int] + checkAll("NonEmptyList[ListWrapper[Int]]", OrderLaws[NonEmptyList[ListWrapper[Int]]].partialOrder) + checkAll("PartialOrder[NonEmptyList[ListWrapper[Int]]]", SerializableTests.serializable(PartialOrder[NonEmptyList[ListWrapper[Int]]])) + + Eq[NonEmptyList[ListWrapper[Int]]] + } + + { + implicit val A = ListWrapper.order[Int] + checkAll("NonEmptyList[ListWrapper[Int]]", OrderLaws[NonEmptyList[ListWrapper[Int]]].order) + checkAll("Order[NonEmptyList[ListWrapper[Int]]]", SerializableTests.serializable(Order[NonEmptyList[ListWrapper[Int]]])) + + Eq[NonEmptyList[ListWrapper[Int]]] + PartialOrder[NonEmptyList[ListWrapper[Int]]] + } + + test("Show is not empty and is formatted as expected") { + forAll { (nel: NonEmptyList[Int]) => + nel.show.nonEmpty should === (true) + nel.show.startsWith("NonEmptyList(") should === (true) + nel.show should === (implicitly[Show[NonEmptyList[Int]]].show(nel)) + nel.show.contains(nel.head.show) should === (true) + } + } + + test("Show is formatted correctly") { + val nonEmptyList = NonEmptyList("Test", Nil) + nonEmptyList.show should === ("NonEmptyList(Test)") + } + + test("Creating NonEmptyList + toList is identity") { + forAll { (i: Int, tail: List[Int]) => + val list = i :: tail + val nonEmptyList = NonEmptyList(i, tail: _*) + list should === (nonEmptyList.toList) + } + } + + test("NonEmptyList#filter is consistent with List#filter") { + forAll { (nel: NonEmptyList[Int], p: Int => Boolean) => + val list = nel.toList + nel.filter(p) should === (list.filter(p)) + } + } + + test("NonEmptyList#find is consistent with List#find") { + forAll { (nel: NonEmptyList[Int], p: Int => Boolean) => + val list = nel.toList + nel.find(p) should === (list.find(p)) + } + } + + test("NonEmptyList#exists is consistent with List#exists") { + forAll { (nel: NonEmptyList[Int], p: Int => Boolean) => + val list = nel.toList + nel.exists(p) should === (list.exists(p)) + } + } + + test("NonEmptyList#forall is consistent with List#forall") { + forAll { (nel: NonEmptyList[Int], p: Int => Boolean) => + val list = nel.toList + nel.forall(p) should === (list.forall(p)) + } + } + + test("NonEmptyList#map is consistent with List#map") { + forAll { (nel: NonEmptyList[Int], p: Int => String) => + val list = nel.toList + nel.map(p).toList should === (list.map(p)) + } + } + + test("reduceLeft consistent with foldLeft") { + forAll { (nel: NonEmptyList[Int], f: (Int, Int) => Int) => + nel.reduceLeft(f) should === (nel.tail.foldLeft(nel.head)(f)) + } + } + + test("reduceRight consistent with foldRight") { + forAll { (nel: NonEmptyList[Int], f: (Int, Eval[Int]) => Eval[Int]) => + nel.reduceRight(f).value should === (nel.tail.foldRight(nel.head)((a, b) => f(a, Now(b)).value)) + } + } + + test("reduce consistent with fold") { + forAll { (nel: NonEmptyList[Int]) => + nel.reduce should === (nel.fold) + } + } + + test("reduce consistent with reduceK") { + forAll { (nel: NonEmptyList[Option[Int]]) => + nel.reduce(SemigroupK[Option].algebra[Int]) should === (nel.reduceK) + } + } + + test("reduceLeftToOption consistent with foldLeft + Option") { + forAll { (nel: NonEmptyList[Int], f: Int => String, g: (String, Int) => String) => + val expected = nel.tail.foldLeft(Option(f(nel.head))) { (opt, i) => + opt.map(s => g(s, i)) + } + nel.reduceLeftToOption(f)(g) should === (expected) + } + } + + test("reduceRightToOption consistent with foldRight + Option") { + forAll { (nel: NonEmptyList[Int], f: Int => String, g: (Int, Eval[String]) => Eval[String]) => + val expected = nel.tail.foldRight(Option(f(nel.head))) { (i, opt) => + opt.map(s => g(i, Now(s)).value) + } + nel.reduceRightToOption(f)(g).value should === (expected) + } + } + + test("fromList round trip") { + forAll { l: List[Int] => + NonEmptyList.fromList(l).map(_.toList).getOrElse(List.empty) should === (l) + } + + forAll { nel: NonEmptyList[Int] => + NonEmptyList.fromList(nel.toList) should === (Some(nel)) + } + } + + test("fromListUnsafe/fromList consistency") { + forAll { nel: NonEmptyList[Int] => + NonEmptyList.fromList(nel.toList) should === (Some(NonEmptyList.fromListUnsafe(nel.toList))) + } + } + + test("fromListUnsafe empty list") { + val _ = intercept[IllegalArgumentException] { + NonEmptyList.fromListUnsafe(List.empty[Int]) + } + } + + test(":: consistent with List") { + forAll { (nel: NonEmptyList[Int], i: Int) => + (i :: nel).toList should === (i :: nel.toList) + } + } +} + +class ReducibleNonEmptyListCheck extends ReducibleCheck[NonEmptyList]("NonEmptyList") { + def iterator[T](nel: NonEmptyList[T]): Iterator[T] = nel.toList.iterator + + def range(start: Long, endInclusive: Long): NonEmptyList[Long] = { + // if we inline this we get a bewildering implicit numeric widening + // error message in Scala 2.10 + val tailStart: Long = start + 1L + NonEmptyList(start, (tailStart).to(endInclusive).toList) + } + +} diff --git a/tests/src/test/scala/cats/tests/NonEmptyVectorTests.scala b/tests/src/test/scala/cats/tests/NonEmptyVectorTests.scala index 7710ac5bf8..5fd419252f 100644 --- a/tests/src/test/scala/cats/tests/NonEmptyVectorTests.scala +++ b/tests/src/test/scala/cats/tests/NonEmptyVectorTests.scala @@ -219,3 +219,14 @@ class NonEmptyVectorTests extends CatsSuite { } } } + +class ReducibleNonEmptyVectorCheck extends ReducibleCheck[NonEmptyVector]("NonEmptyVector") { + def iterator[T](nel: NonEmptyVector[T]): Iterator[T] = nel.toVector.iterator + + def range(start: Long, endInclusive: Long): NonEmptyVector[Long] = { + // if we inline this we get a bewildering implicit numeric widening + // error message in Scala 2.10 + val tailStart: Long = start + 1L + NonEmptyVector(start, (tailStart).to(endInclusive).toVector) + } +} diff --git a/tests/src/test/scala/cats/tests/OneAndTests.scala b/tests/src/test/scala/cats/tests/OneAndTests.scala index 7ddb55ef19..79d8ec4004 100644 --- a/tests/src/test/scala/cats/tests/OneAndTests.scala +++ b/tests/src/test/scala/cats/tests/OneAndTests.scala @@ -3,7 +3,7 @@ package tests import cats.kernel.laws.{GroupLaws, OrderLaws} -import cats.data.{NonEmptyList, OneAnd} +import cats.data.{NonEmptyStream, OneAnd} import cats.laws.discipline.{ComonadTests, FunctorTests, SemigroupKTests, FoldableTests, MonadTests, SerializableTests, CartesianTests, TraverseTests, ReducibleTests} import cats.laws.discipline.arbitrary._ @@ -12,13 +12,13 @@ class OneAndTests extends CatsSuite { implicit override val generatorDrivenConfig: PropertyCheckConfiguration = PropertyCheckConfig(maxSize = 5, minSuccessful = 20) - checkAll("OneAnd[List, Int]", OrderLaws[OneAnd[List, Int]].eqv) + checkAll("OneAnd[Stream, Int]", OrderLaws[OneAnd[Stream, Int]].eqv) - checkAll("OneAnd[List, Int] with Option", TraverseTests[OneAnd[List, ?]].traverse[Int, Int, Int, Int, Option, Option]) - checkAll("Traverse[OneAnd[List, A]]", SerializableTests.serializable(Traverse[OneAnd[List, ?]])) + checkAll("OneAnd[Stream, Int] with Option", TraverseTests[OneAnd[Stream, ?]].traverse[Int, Int, Int, Int, Option, Option]) + checkAll("Traverse[OneAnd[Stream, A]]", SerializableTests.serializable(Traverse[OneAnd[Stream, ?]])) - checkAll("OneAnd[List, Int]", ReducibleTests[OneAnd[List, ?]].reducible[Option, Int, Int]) - checkAll("Reducible[OneAnd[List, ?]]", SerializableTests.serializable(Reducible[OneAnd[List, ?]])) + checkAll("OneAnd[Stream, Int]", ReducibleTests[OneAnd[Stream, ?]].reducible[Option, Int, Int]) + checkAll("Reducible[OneAnd[Stream, ?]]", SerializableTests.serializable(Reducible[OneAnd[Stream, ?]])) implicit val iso = CartesianTests.Isomorphisms.invariant[OneAnd[ListWrapper, ?]](OneAnd.catsDataFunctorForOneAnd(ListWrapper.functor)) @@ -38,9 +38,9 @@ class OneAndTests extends CatsSuite { { implicit val monadCombine = ListWrapper.monadCombine checkAll("OneAnd[ListWrapper, Int]", SemigroupKTests[OneAnd[ListWrapper, ?]].semigroupK[Int]) - checkAll("OneAnd[List, Int]", GroupLaws[OneAnd[List, Int]].semigroup) + checkAll("OneAnd[Stream, Int]", GroupLaws[OneAnd[Stream, Int]].semigroup) checkAll("SemigroupK[OneAnd[ListWrapper, A]]", SerializableTests.serializable(SemigroupK[OneAnd[ListWrapper, ?]])) - checkAll("Semigroup[NonEmptyList[Int]]", SerializableTests.serializable(Semigroup[OneAnd[List, Int]])) + checkAll("Semigroup[NonEmptyStream[Int]]", SerializableTests.serializable(Semigroup[OneAnd[Stream, Int]])) } { @@ -51,18 +51,18 @@ class OneAndTests extends CatsSuite { { // Test functor and subclasses don't have implicit conflicts - implicitly[Functor[NonEmptyList]] - implicitly[Monad[NonEmptyList]] - implicitly[Comonad[NonEmptyList]] + implicitly[Functor[NonEmptyStream]] + implicitly[Monad[NonEmptyStream]] + implicitly[Comonad[NonEmptyStream]] } - implicit val iso2 = CartesianTests.Isomorphisms.invariant[OneAnd[List, ?]] + implicit val iso2 = CartesianTests.Isomorphisms.invariant[OneAnd[Stream, ?]] - checkAll("NonEmptyList[Int]", MonadTests[NonEmptyList].monad[Int, Int, Int]) - checkAll("Monad[NonEmptyList[A]]", SerializableTests.serializable(Monad[NonEmptyList])) + checkAll("NonEmptyStream[Int]", MonadTests[NonEmptyStream].monad[Int, Int, Int]) + checkAll("Monad[NonEmptyStream[A]]", SerializableTests.serializable(Monad[NonEmptyStream])) - checkAll("NonEmptyList[Int]", ComonadTests[NonEmptyList].comonad[Int, Int, Int]) - checkAll("Comonad[NonEmptyList[A]]", SerializableTests.serializable(Comonad[NonEmptyList])) + checkAll("NonEmptyStream[Int]", ComonadTests[NonEmptyStream].comonad[Int, Int, Int]) + checkAll("Comonad[NonEmptyStream[A]]", SerializableTests.serializable(Comonad[NonEmptyStream])) test("size is consistent with toList.size") { forAll { (oa: OneAnd[Vector, Int]) => @@ -71,88 +71,88 @@ class OneAndTests extends CatsSuite { } test("Show is not empty and is formatted as expected") { - forAll { (nel: NonEmptyList[Int]) => + forAll { (nel: NonEmptyStream[Int]) => nel.show.nonEmpty should === (true) nel.show.startsWith("OneAnd(") should === (true) - nel.show should === (implicitly[Show[NonEmptyList[Int]]].show(nel)) + nel.show should === (implicitly[Show[NonEmptyStream[Int]]].show(nel)) nel.show.contains(nel.head.show) should === (true) } } test("Show is formatted correctly") { - val oneAnd = NonEmptyList("Test", Nil) - oneAnd.show should === ("OneAnd(Test, List())") + val oneAnd = NonEmptyStream("Test") + oneAnd.show should === ("OneAnd(Test, Stream())") } test("Creating OneAnd + unwrap is identity") { - forAll { (i: Int, tail: List[Int]) => - val list = i :: tail - val oneAnd = NonEmptyList(i, tail: _*) - list should === (oneAnd.unwrap) + forAll { (i: Int, tail: Stream[Int]) => + val stream = i #:: tail + val oneAnd = NonEmptyStream(i, tail: _*) + stream should === (oneAnd.unwrap) } } - test("NonEmptyList#filter is consistent with List#filter") { - forAll { (nel: NonEmptyList[Int], p: Int => Boolean) => - val list = nel.unwrap - nel.filter(p) should === (list.filter(p)) + test("NonEmptyStream#filter is consistent with Stream#filter") { + forAll { (nel: NonEmptyStream[Int], p: Int => Boolean) => + val stream = nel.unwrap + nel.filter(p) should === (stream.filter(p)) } } - test("NonEmptyList#find is consistent with List#find") { - forAll { (nel: NonEmptyList[Int], p: Int => Boolean) => - val list = nel.unwrap - nel.find(p) should === (list.find(p)) + test("NonEmptyStream#find is consistent with Stream#find") { + forAll { (nel: NonEmptyStream[Int], p: Int => Boolean) => + val stream = nel.unwrap + nel.find(p) should === (stream.find(p)) } } - test("NonEmptyList#exists is consistent with List#exists") { - forAll { (nel: NonEmptyList[Int], p: Int => Boolean) => - val list = nel.unwrap - nel.exists(p) should === (list.exists(p)) + test("NonEmptyStream#exists is consistent with Stream#exists") { + forAll { (nel: NonEmptyStream[Int], p: Int => Boolean) => + val stream = nel.unwrap + nel.exists(p) should === (stream.exists(p)) } } - test("NonEmptyList#forall is consistent with List#forall") { - forAll { (nel: NonEmptyList[Int], p: Int => Boolean) => - val list = nel.unwrap - nel.forall(p) should === (list.forall(p)) + test("NonEmptyStream#forall is consistent with Stream#forall") { + forAll { (nel: NonEmptyStream[Int], p: Int => Boolean) => + val stream = nel.unwrap + nel.forall(p) should === (stream.forall(p)) } } - test("NonEmptyList#map is consistent with List#map") { - forAll { (nel: NonEmptyList[Int], p: Int => String) => - val list = nel.unwrap - nel.map(p).unwrap should === (list.map(p)) + test("NonEmptyStream#map is consistent with Stream#map") { + forAll { (nel: NonEmptyStream[Int], p: Int => String) => + val stream = nel.unwrap + nel.map(p).unwrap should === (stream.map(p)) } } test("reduceLeft consistent with foldLeft") { - forAll { (nel: NonEmptyList[Int], f: (Int, Int) => Int) => + forAll { (nel: NonEmptyStream[Int], f: (Int, Int) => Int) => nel.reduceLeft(f) should === (nel.tail.foldLeft(nel.head)(f)) } } test("reduceRight consistent with foldRight") { - forAll { (nel: NonEmptyList[Int], f: (Int, Eval[Int]) => Eval[Int]) => + forAll { (nel: NonEmptyStream[Int], f: (Int, Eval[Int]) => Eval[Int]) => nel.reduceRight(f).value should === (nel.tail.foldRight(nel.head)((a, b) => f(a, Now(b)).value)) } } test("reduce consistent with fold") { - forAll { (nel: NonEmptyList[Int]) => + forAll { (nel: NonEmptyStream[Int]) => nel.reduce should === (nel.fold) } } test("reduce consistent with reduceK") { - forAll { (nel: NonEmptyList[Option[Int]]) => + forAll { (nel: NonEmptyStream[Option[Int]]) => nel.reduce(SemigroupK[Option].algebra[Int]) should === (nel.reduceK) } } test("reduceLeftToOption consistent with foldLeft + Option") { - forAll { (nel: NonEmptyList[Int], f: Int => String, g: (String, Int) => String) => + forAll { (nel: NonEmptyStream[Int], f: Int => String, g: (String, Int) => String) => val expected = nel.tail.foldLeft(Option(f(nel.head))) { (opt, i) => opt.map(s => g(s, i)) } @@ -161,7 +161,7 @@ class OneAndTests extends CatsSuite { } test("reduceRightToOption consistent with foldRight + Option") { - forAll { (nel: NonEmptyList[Int], f: Int => String, g: (Int, Eval[String]) => Eval[String]) => + forAll { (nel: NonEmptyStream[Int], f: Int => String, g: (Int, Eval[String]) => Eval[String]) => val expected = nel.tail.foldRight(Option(f(nel.head))) { (i, opt) => opt.map(s => g(i, Now(s)).value) } diff --git a/tests/src/test/scala/cats/tests/ReducibleTests.scala b/tests/src/test/scala/cats/tests/ReducibleTests.scala index ede3ac68b9..759b2f30fb 100644 --- a/tests/src/test/scala/cats/tests/ReducibleTests.scala +++ b/tests/src/test/scala/cats/tests/ReducibleTests.scala @@ -1,6 +1,8 @@ package cats package tests +import org.scalacheck.Arbitrary + class ReducibleTestsAdditional extends CatsSuite { test("Reducible[NonEmptyList].reduceLeftM stack safety") { @@ -15,4 +17,22 @@ class ReducibleTestsAdditional extends CatsSuite { } +abstract class ReducibleCheck[F[_]: Reducible](name: String)(implicit ArbFInt: Arbitrary[F[Int]]) extends FoldableCheck[F](name) { + def range(start: Long, endInclusive: Long): F[Long] + + test(s"Reducible[$name].reduceLeftM stack safety") { + def nonzero(acc: Long, x: Long): Option[Long] = + if (x == 0) None else Some(acc + x) + val n = 100000L + val expected = n*(n+1)/2 + val actual = range(1L, n).reduceLeftM(Option.apply)(nonzero) + actual should === (Some(expected)) + } + + test(s"Reducible[$name].toNonEmptyList/toList consistency") { + forAll { fa: F[Int] => + fa.toList.toNel should === (Some(fa.toNonEmptyList)) + } + } +}