From aa8fea732510218ed5e7255391e722924e66a088 Mon Sep 17 00:00:00 2001 From: Deepu Puthrote Date: Sat, 11 Jun 2016 16:16:30 +0100 Subject: [PATCH 01/11] NonEmptyList work in progress --- .../main/scala/cats/data/NonEmptyList.scala | 68 ++++++++ core/src/main/scala/cats/data/package.scala | 13 +- .../scala/cats/tests/NonEmptyListTests.scala | 165 ++++++++++++++++++ 3 files changed, 240 insertions(+), 6 deletions(-) create mode 100644 core/src/main/scala/cats/data/NonEmptyList.scala create mode 100644 tests/src/test/scala/cats/tests/NonEmptyListTests.scala 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..c2fb91f422 --- /dev/null +++ b/core/src/main/scala/cats/data/NonEmptyList.scala @@ -0,0 +1,68 @@ +package cats +package data + +import cats.std.list._ +//it needs a Functor +//it needs semigroup - combine, filter + +/** + * A data type which represents a single element (head) and some other + * structure (tail). + */ +final case class NonEmptyList[A](head: A, tail: List[A]) { + + /** + * Return the head and tail into a single list + */ + def unwrap: List[A] = head :: tail + + /** + * remove elements not matching the predicate + */ + def filter(p: A => Boolean): List[A] = { + val rest = tail.filter(p) + if(p(head)) head::rest else rest + } + + /** + * Append another NonEmptyList + */ + def combine(other: NonEmptyList[A]):NonEmptyList[A] = + NonEmptyList(head, MonadCombine[List].combineK(tail, other.head::other.tail)) + + /** + * 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.exists(p) + + /** + * Left-associative fold on the structure using f. + */ + def foldLeft[B](b: B)(f: (B, A) => B): B = + (head::tail).foldLeft(b)(f) + + /** + * Right-associative fold on the structure using f. + */ + def foldRight[B](lb: Eval[B])(f: (A, Eval[B]) => Eval[B]):Eval[B] = + (head::tail).foldRight(lb)(f) + + /** + * Applies f to all the elements of the structure + */ + def map[B](f: A => B):NonEmptyList[B] = + NonEmptyList(f(head), tail.map(f)) +} diff --git a/core/src/main/scala/cats/data/package.scala b/core/src/main/scala/cats/data/package.scala index de3c882952..f4b92362d9 100644 --- a/core/src/main/scala/cats/data/package.scala +++ b/core/src/main/scala/cats/data/package.scala @@ -1,15 +1,15 @@ package cats package object data { - type NonEmptyList[A] = OneAnd[List, A] +// type NonEmptyList[A] = OneAnd[List, A] type NonEmptyVector[A] = OneAnd[Vector, 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 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 NonEmptyVector[A](head: A, tail: Vector[A] = Vector.empty): NonEmptyVector[A] = OneAnd(head, tail) @@ -21,6 +21,7 @@ package object data { 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) => @@ -32,7 +33,7 @@ package object data { 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/tests/src/test/scala/cats/tests/NonEmptyListTests.scala b/tests/src/test/scala/cats/tests/NonEmptyListTests.scala new file mode 100644 index 0000000000..df2d272d5a --- /dev/null +++ b/tests/src/test/scala/cats/tests/NonEmptyListTests.scala @@ -0,0 +1,165 @@ +package cats +package tests + +import cats.kernel.laws.{GroupLaws, OrderLaws} + +import cats.data.{NonEmptyList, NonEmptyList} +import cats.laws.discipline.{ComonadTests, FunctorTests, SemigroupKTests, FoldableTests, MonadTests, SerializableTests, CartesianTests, 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[List, Int]", OrderLaws[NonEmptyList[List, Int]].eqv) + + checkAll("NonEmptyList[List, Int] with Option", TraverseTests[NonEmptyList[List, ?]].traverse[Int, Int, Int, Int, Option, Option]) + checkAll("Traverse[NonEmptyList[List, A]]", SerializableTests.serializable(Traverse[NonEmptyList[List, ?]])) + + checkAll("NonEmptyList[List, Int]", ReducibleTests[NonEmptyList[List, ?]].reducible[Option, Int, Int]) + checkAll("Reducible[NonEmptyList[List, ?]]", SerializableTests.serializable(Reducible[NonEmptyList[List, ?]])) + + implicit val iso = CartesianTests.Isomorphisms.invariant[NonEmptyList[ListWrapper, ?]](NonEmptyList.catsDataFunctorForNonEmptyList(ListWrapper.functor)) + + // Test instances that have more general constraints + { + implicit val monadCombine = ListWrapper.monadCombine + checkAll("NonEmptyList[ListWrapper, Int]", CartesianTests[NonEmptyList[ListWrapper, ?]].cartesian[Int, Int, Int]) + checkAll("Cartesian[NonEmptyList[ListWrapper, A]]", SerializableTests.serializable(Cartesian[NonEmptyList[ListWrapper, ?]])) + } + + { + implicit val functor = ListWrapper.functor + checkAll("NonEmptyList[ListWrapper, Int]", FunctorTests[NonEmptyList[ListWrapper, ?]].functor[Int, Int, Int]) + checkAll("Functor[NonEmptyList[ListWrapper, A]]", SerializableTests.serializable(Functor[NonEmptyList[ListWrapper, ?]])) + } + + { + implicit val monadCombine = ListWrapper.monadCombine + checkAll("NonEmptyList[ListWrapper, Int]", SemigroupKTests[NonEmptyList[ListWrapper, ?]].semigroupK[Int]) + checkAll("NonEmptyList[List, Int]", GroupLaws[NonEmptyList[List, Int]].semigroup) + checkAll("SemigroupK[NonEmptyList[ListWrapper, A]]", SerializableTests.serializable(SemigroupK[NonEmptyList[ListWrapper, ?]])) + checkAll("Semigroup[NonEmptyList[Int]]", SerializableTests.serializable(Semigroup[NonEmptyList[List, Int]])) + } + + { + implicit val foldable = ListWrapper.foldable + checkAll("NonEmptyList[ListWrapper, Int]", FoldableTests[NonEmptyList[ListWrapper, ?]].foldable[Int, Int]) + checkAll("Foldable[NonEmptyList[ListWrapper, A]]", SerializableTests.serializable(Foldable[NonEmptyList[ListWrapper, ?]])) + } + + { + // Test functor and subclasses don't have implicit conflicts + implicitly[Functor[NonEmptyList]] + implicitly[Monad[NonEmptyList]] + implicitly[Comonad[NonEmptyList]] + } + + implicit val iso2 = CartesianTests.Isomorphisms.invariant[NonEmptyList[List, ?]] + + checkAll("NonEmptyList[Int]", MonadTests[NonEmptyList].monad[Int, Int, Int]) + checkAll("Monad[NonEmptyList[A]]", SerializableTests.serializable(Monad[NonEmptyList])) + + checkAll("NonEmptyList[Int]", ComonadTests[NonEmptyList].comonad[Int, Int, Int]) + checkAll("Comonad[NonEmptyList[A]]", SerializableTests.serializable(Comonad[NonEmptyList])) + + 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, List())") + } + + test("Creating NonEmptyList + unwrap is identity") { + forAll { (i: Int, tail: List[Int]) => + val list = i :: tail + val nonEmptyList = NonEmptyList(i, tail: _*) + list should === (nonEmptyList.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("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("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("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("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("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) + } + } +} From 70187da4fa57d68c92353696208a4b828a31bfc0 Mon Sep 17 00:00:00 2001 From: Deepu Puthrote Date: Mon, 13 Jun 2016 14:39:20 +0100 Subject: [PATCH 02/11] Added instances for NonEmptyList #1087 work in progress --- .../main/scala/cats/data/NonEmptyList.scala | 153 ++++++++++++++---- 1 file changed, 122 insertions(+), 31 deletions(-) diff --git a/core/src/main/scala/cats/data/NonEmptyList.scala b/core/src/main/scala/cats/data/NonEmptyList.scala index c2fb91f422..53f59d6ef6 100644 --- a/core/src/main/scala/cats/data/NonEmptyList.scala +++ b/core/src/main/scala/cats/data/NonEmptyList.scala @@ -6,63 +6,154 @@ import cats.std.list._ //it needs semigroup - combine, filter /** - * A data type which represents a single element (head) and some other - * structure (tail). - */ -final case class NonEmptyList[A](head: A, tail: List[A]) { + * A data type which represents a single element (head) and some other + * structure (tail). + */ +final case class NonEmptyList[A](head: A, tail: List[A] = List[A]()) { /** - * Return the head and tail into a single list - */ + * Return the head and tail into a single list + */ def unwrap: List[A] = head :: tail /** - * remove elements not matching the predicate - */ + * remove elements not matching the predicate + */ def filter(p: A => Boolean): List[A] = { val rest = tail.filter(p) - if(p(head)) head::rest else rest + if (p(head)) head :: rest else rest } /** - * Append another NonEmptyList - */ - def combine(other: NonEmptyList[A]):NonEmptyList[A] = - NonEmptyList(head, MonadCombine[List].combineK(tail, other.head::other.tail)) + * Append another NonEmptyList + */ + def combine(other: NonEmptyList[A]): NonEmptyList[A] = + NonEmptyList(head, MonadCombine[List].combineK(tail, other.head :: other.tail)) /** - * 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) + * 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 - */ + * 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 - */ + * Check whether all elements satisfy the predicate + */ def forall(p: A => Boolean): Boolean = p(head) && tail.exists(p) /** - * Left-associative fold on the structure using f. - */ + * Left-associative fold on the structure using f. + */ def foldLeft[B](b: B)(f: (B, A) => B): B = - (head::tail).foldLeft(b)(f) + (head :: tail).foldLeft(b)(f) /** - * Right-associative fold on the structure using f. - */ - def foldRight[B](lb: Eval[B])(f: (A, Eval[B]) => Eval[B]):Eval[B] = - (head::tail).foldRight(lb)(f) + * Right-associative fold on the structure using f. + */ + def foldRight[B](lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = + (head :: tail).foldRight(lb)(f) /** - * Applies f to all the elements of the structure - */ - def map[B](f: A => B):NonEmptyList[B] = + * Applies f to all the elements of the structure + */ + def map[B](f: A => B): NonEmptyList[B] = NonEmptyList(f(head), tail.map(f)) } + +private[data] sealed trait NonEmptyListInstances extends NonEmptyListLowPriority2 { + + implicit def catsDataEqForNonEmptyList[A](implicit A: Eq[A]): Eq[NonEmptyList[A]] = + new Eq[NonEmptyList[A]]{ + def eqv(x: NonEmptyList[A], y: NonEmptyList[A]): Boolean = x === y + } + + implicit def catsDataShowForNonEmptyList[A](implicit A: Show[A]): Show[NonEmptyList[A]] = + Show.show[NonEmptyList[A]](_.show) + + implicit def catsDataSemigroupKForNonEmptyList[A]: SemigroupK[NonEmptyList[?]] = + new SemigroupK[NonEmptyList[?]] { + def combineK[A](a: NonEmptyList[A], b: NonEmptyList[A]): NonEmptyList[A] = + a combine b + } + + implicit def catsDataSemigroupForNonEmptyList[A]: Semigroup[NonEmptyList[A]] = + catsDataSemigroupKForNonEmptyList[F].algebra + + implicit def catsDataReducibleForNonEmptyList[A]: Reducible[NonEmptyList[?]] = + new NonEmptyReducible[NonEmptyList[?]] { + override def split[A](fa: NonEmptyList[A]): (A, F[A]) = (fa.head, fa.tail) + } + + implicit def catsDataMonadForNonEmptyList[A]: Monad[NonEmptyList[?]] = + new Monad[NonEmptyList[?]] { + override def map[A, B](fa: NonEmptyList[F, A])(f: A => B): NonEmptyList[F, B] = + fa map f + + def pure[A](x: A): NonEmptyList[F, A] = + NonEmptyList(x, monad.empty) + + def flatMap[A, B](fa: NonEmptyList[F, A])(f: A => NonEmptyList[F, B]): NonEmptyList[F, B] = { + val end = monad.flatMap(fa.tail) { a => + val fa = f(a) + monad.combineK(monad.pure(fa.head), fa.tail) + } + val fst = f(fa.head) + NonEmptyList(fst.head, monad.combineK(fst.tail, end)) + } + } +} + +trait NonEmptyListLowPriority0 { + implicit val nelComonad: Comonad[NonEmptyList[List, ?]] = + new Comonad[NonEmptyList[List, ?]] { + def coflatMap[A, B](fa: NonEmptyList[List, A])(f: NonEmptyList[List, A] => B): NonEmptyList[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(NonEmptyList(a, as))) + } + NonEmptyList(f(fa), consume(fa.tail, ListBuffer.empty)) + } + + def extract[A](fa: NonEmptyList[List, A]): A = + fa.head + + def map[A, B](fa: NonEmptyList[List, A])(f: A => B): NonEmptyList[List, B] = + fa map f + } +} + +trait NonEmptyListLowPriority1 extends NonEmptyListLowPriority0 { + implicit def catsDataFunctorForNonEmptyList[F[_]](implicit F: Functor[F]): Functor[NonEmptyList[F, ?]] = + new Functor[NonEmptyList[F, ?]] { + def map[A, B](fa: NonEmptyList[F, A])(f: A => B): NonEmptyList[F, B] = + fa map f + } + +} + +trait NonEmptyListLowPriority2 extends NonEmptyListLowPriority1 { + implicit def catsDataTraverseForNonEmptyList[F[_]](implicit F: Traverse[F]): Traverse[NonEmptyList[F, ?]] = + new Traverse[NonEmptyList[F, ?]] { + def traverse[G[_], A, B](fa: NonEmptyList[F, A])(f: (A) => G[B])(implicit G: Applicative[G]): G[NonEmptyList[F, B]] = { + G.map2Eval(f(fa.head), Always(F.traverse(fa.tail)(f)))(NonEmptyList(_, _)).value + } + + def foldLeft[A, B](fa: NonEmptyList[F, A], b: B)(f: (B, A) => B): B = { + fa.foldLeft(b)(f) + } + + def foldRight[A, B](fa: NonEmptyList[F, A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = { + fa.foldRight(lb)(f) + } + } +} + +object NonEmptyList extends NonEmptyListInstances From 1c2adb17dc5dc23b973654c68db964a54daf0460 Mon Sep 17 00:00:00 2001 From: Deepu Puthrote Date: Mon, 13 Jun 2016 14:40:49 +0100 Subject: [PATCH 03/11] Review comment - rename unwrap to toList --- core/src/main/scala/cats/data/NonEmptyList.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/data/NonEmptyList.scala b/core/src/main/scala/cats/data/NonEmptyList.scala index 53f59d6ef6..08920bec43 100644 --- a/core/src/main/scala/cats/data/NonEmptyList.scala +++ b/core/src/main/scala/cats/data/NonEmptyList.scala @@ -14,7 +14,7 @@ final case class NonEmptyList[A](head: A, tail: List[A] = List[A]()) { /** * Return the head and tail into a single list */ - def unwrap: List[A] = head :: tail + def toList: List[A] = head :: tail /** * remove elements not matching the predicate From 4e8ffe1986f825fe4633f2733d96e5a3dfb8fccf Mon Sep 17 00:00:00 2001 From: Deepu Puthrote Date: Tue, 14 Jun 2016 00:02:40 +0100 Subject: [PATCH 04/11] Code review changes for #1087 - Use toList instead of head::tail - Use tail ::: other.toList, instead of MonadCombine - Define apply method instead of using default paramter for tail - Use Nil instead of monad.empty - Simplify find and filter --- .../main/scala/cats/data/NonEmptyList.scala | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/core/src/main/scala/cats/data/NonEmptyList.scala b/core/src/main/scala/cats/data/NonEmptyList.scala index 08920bec43..16119d10c8 100644 --- a/core/src/main/scala/cats/data/NonEmptyList.scala +++ b/core/src/main/scala/cats/data/NonEmptyList.scala @@ -1,15 +1,11 @@ package cats package data -import cats.std.list._ -//it needs a Functor -//it needs semigroup - combine, filter - /** - * A data type which represents a single element (head) and some other - * structure (tail). + * 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] = List[A]()) { +final case class NonEmptyList[A](head: A, tail: List[A]) { /** * Return the head and tail into a single list @@ -19,22 +15,20 @@ final case class NonEmptyList[A](head: A, tail: List[A] = List[A]()) { /** * remove elements not matching the predicate */ - def filter(p: A => Boolean): List[A] = { - val rest = tail.filter(p) - if (p(head)) head :: rest else rest - } + def filter(p: A => Boolean): List[A] = + toList.filter(p) /** * Append another NonEmptyList */ def combine(other: NonEmptyList[A]): NonEmptyList[A] = - NonEmptyList(head, MonadCombine[List].combineK(tail, other.head :: other.tail)) + 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) + toList.find(p) /** * Check whether at least one element satisfies the predicate @@ -52,13 +46,13 @@ final case class NonEmptyList[A](head: A, tail: List[A] = List[A]()) { * Left-associative fold on the structure using f. */ def foldLeft[B](b: B)(f: (B, A) => B): B = - (head :: tail).foldLeft(b)(f) + 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] = - (head :: tail).foldRight(lb)(f) + toList.foldRight(lb)(f) /** * Applies f to all the elements of the structure @@ -69,8 +63,10 @@ final case class NonEmptyList[A](head: A, tail: List[A] = List[A]()) { private[data] sealed trait NonEmptyListInstances extends NonEmptyListLowPriority2 { + def apply[A](head: A, tail: A*): NonEmptyList[A] = NonEmptyList(head, tail.toList) + implicit def catsDataEqForNonEmptyList[A](implicit A: Eq[A]): Eq[NonEmptyList[A]] = - new Eq[NonEmptyList[A]]{ + new Eq[NonEmptyList[A]] { def eqv(x: NonEmptyList[A], y: NonEmptyList[A]): Boolean = x === y } @@ -97,7 +93,7 @@ private[data] sealed trait NonEmptyListInstances extends NonEmptyListLowPriority fa map f def pure[A](x: A): NonEmptyList[F, A] = - NonEmptyList(x, monad.empty) + NonEmptyList(x, Nil) def flatMap[A, B](fa: NonEmptyList[F, A])(f: A => NonEmptyList[F, B]): NonEmptyList[F, B] = { val end = monad.flatMap(fa.tail) { a => From 9eea8e21234f6ba3e6e076fcf6e8ca55bfcd1217 Mon Sep 17 00:00:00 2001 From: Deepu Puthrote Date: Tue, 14 Jun 2016 00:20:43 +0100 Subject: [PATCH 05/11] Added TODOs based on review comments #1087 --- core/src/main/scala/cats/data/NonEmptyList.scala | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/src/main/scala/cats/data/NonEmptyList.scala b/core/src/main/scala/cats/data/NonEmptyList.scala index 16119d10c8..6a0f8c5774 100644 --- a/core/src/main/scala/cats/data/NonEmptyList.scala +++ b/core/src/main/scala/cats/data/NonEmptyList.scala @@ -56,6 +56,8 @@ final case class NonEmptyList[A](head: A, tail: List[A]) { /** * Applies f to all the elements of the structure + * TODO It would be nice to have variance on this particular method def map[AA <: A, B](f: AA => B): NonEmptyList[B] so that you can pass a function for a supertype of A into map. @yilinwei + * https://github.com/typelevel/cats/pull/1120#discussion-diff-66881573 */ def map[B](f: A => B): NonEmptyList[B] = NonEmptyList(f(head), tail.map(f)) @@ -95,6 +97,7 @@ private[data] sealed trait NonEmptyListInstances extends NonEmptyListLowPriority def pure[A](x: A): NonEmptyList[F, A] = NonEmptyList(x, Nil) + // TODO Could we move this method (and other type class methods) to NonEmptyList and then reference them in the instances? I think that will make the scaladoc for NonEmptyList a bit nicer. @non def flatMap[A, B](fa: NonEmptyList[F, A])(f: A => NonEmptyList[F, B]): NonEmptyList[F, B] = { val end = monad.flatMap(fa.tail) { a => val fa = f(a) @@ -102,6 +105,9 @@ private[data] sealed trait NonEmptyListInstances extends NonEmptyListLowPriority } val fst = f(fa.head) NonEmptyList(fst.head, monad.combineK(fst.tail, end)) + // TODO @yilinwei https://github.com/typelevel/cats/pull/1120#discussion_r66882139 + // val xs = f(head) ++ tail.flatMap(f.andThen(_.toList)) + // NonEmptyList(xs.head, xs.tail) } } } From 4acdc86925b404a6816f0faf1a894844a3ef4fdb Mon Sep 17 00:00:00 2001 From: Deepu Puthrote Date: Tue, 14 Jun 2016 00:23:19 +0100 Subject: [PATCH 06/11] Removed commented out code from package.scala #1087 --- core/src/main/scala/cats/data/package.scala | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/core/src/main/scala/cats/data/package.scala b/core/src/main/scala/cats/data/package.scala index f4b92362d9..e7cc91eada 100644 --- a/core/src/main/scala/cats/data/package.scala +++ b/core/src/main/scala/cats/data/package.scala @@ -1,16 +1,10 @@ package cats package object data { -// type NonEmptyList[A] = OneAnd[List, A] type NonEmptyVector[A] = OneAnd[Vector, 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 NonEmptyVector[A](head: A, tail: Vector[A] = Vector.empty): NonEmptyVector[A] = OneAnd(head, tail) def NonEmptyVector[A](head: A, tail: A*): NonEmptyVector[A] = @@ -21,20 +15,6 @@ package object data { 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 From d788e628160d94ae1cf22912d82161662bcd3dab Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Mon, 25 Jul 2016 11:06:43 -0400 Subject: [PATCH 07/11] Finish up work on NonEmptyList This continues work started by @WarFox in #1120 (see [this comment](https://github.com/typelevel/cats/pull/1120#issuecomment-234737694)). At this point, I have left `OneAnd` in place. However, I think that after merging this we may want to delete it. In practice it's pretty awkward to use and sometimes prevents performant operations. See also #1089. --- core/src/main/scala/cats/Traverse.scala | 4 +- .../main/scala/cats/data/NonEmptyList.scala | 192 ++++++++++-------- core/src/main/scala/cats/data/OneAnd.scala | 25 +-- core/src/main/scala/cats/data/XorT.scala | 2 +- core/src/main/scala/cats/syntax/option.scala | 4 +- .../cats/laws/discipline/Arbitrary.scala | 11 +- .../scala/cats/tests/CokleisliTests.scala | 12 -- .../test/scala/cats/tests/ConstTests.scala | 2 +- .../test/scala/cats/tests/FoldableTests.scala | 4 +- .../src/test/scala/cats/tests/ListTests.scala | 2 +- .../test/scala/cats/tests/NestedTests.scala | 13 +- .../scala/cats/tests/NonEmptyListTests.scala | 54 ++--- .../test/scala/cats/tests/OneAndTests.scala | 100 ++++----- 13 files changed, 223 insertions(+), 202 deletions(-) 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 index 6a0f8c5774..730f34b3b2 100644 --- a/core/src/main/scala/cats/data/NonEmptyList.scala +++ b/core/src/main/scala/cats/data/NonEmptyList.scala @@ -1,6 +1,12 @@ package cats package data +import cats.instances.list._ +import cats.syntax.eq._ + +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). @@ -12,6 +18,18 @@ final case class NonEmptyList[A](head: A, tail: List[A]) { */ 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)) + /** * remove elements not matching the predicate */ @@ -21,7 +39,7 @@ final case class NonEmptyList[A](head: A, tail: List[A]) { /** * Append another NonEmptyList */ - def combine(other: NonEmptyList[A]): NonEmptyList[A] = + def concat(other: NonEmptyList[A]): NonEmptyList[A] = NonEmptyList(head, tail ::: other.toList) /** @@ -40,7 +58,7 @@ final case class NonEmptyList[A](head: A, tail: List[A]) { * Check whether all elements satisfy the predicate */ def forall(p: A => Boolean): Boolean = - p(head) && tail.exists(p) + p(head) && tail.forall(p) /** * Left-associative fold on the structure using f. @@ -52,110 +70,124 @@ final case class NonEmptyList[A](head: A, tail: List[A]) { * Right-associative fold on the structure using f. */ def foldRight[B](lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = - toList.foldRight(lb)(f) + Foldable[List].foldRight(toList, lb)(f) /** - * Applies f to all the elements of the structure - * TODO It would be nice to have variance on this particular method def map[AA <: A, B](f: AA => B): NonEmptyList[B] so that you can pass a function for a supertype of A into map. @yilinwei - * https://github.com/typelevel/cats/pull/1120#discussion-diff-66881573 + * Left-associative reduce using f. */ - def map[B](f: A => B): NonEmptyList[B] = - NonEmptyList(f(head), tail.map(f)) -} + def reduceLeft(f: (A, A) => A): A = + tail.foldLeft(head)(f) + + def coflatMap[B](f: NonEmptyList[A] => B): NonEmptyList[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(NonEmptyList(a, as))) + } + NonEmptyList(f(this), consume(this.tail, ListBuffer.empty)) + } -private[data] sealed trait NonEmptyListInstances extends NonEmptyListLowPriority2 { + 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) - implicit def catsDataEqForNonEmptyList[A](implicit A: Eq[A]): Eq[NonEmptyList[A]] = - new Eq[NonEmptyList[A]] { - def eqv(x: NonEmptyList[A], y: NonEmptyList[A]): Boolean = x === y - } + def fromList[A](l: List[A]): Option[NonEmptyList[A]] = + if (l.isEmpty) None else Some(NonEmptyList(l.head, l.tail)) - implicit def catsDataShowForNonEmptyList[A](implicit A: Show[A]): Show[NonEmptyList[A]] = - Show.show[NonEmptyList[A]](_.show) + def fromListUnsafe[A](l: List[A]): NonEmptyList[A] = + if (l.nonEmpty) NonEmptyList(l.head, l.tail) + else throw new IllegalArgumentException("Cannot create NonEmptyList from empty list") +} + +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] { - implicit def catsDataSemigroupKForNonEmptyList[A]: SemigroupK[NonEmptyList[?]] = - new SemigroupK[NonEmptyList[?]] { def combineK[A](a: NonEmptyList[A], b: NonEmptyList[A]): NonEmptyList[A] = - a combine b - } + a concat b - implicit def catsDataSemigroupForNonEmptyList[A]: Semigroup[NonEmptyList[A]] = - catsDataSemigroupKForNonEmptyList[F].algebra + override def split[A](fa: NonEmptyList[A]): (A, List[A]) = (fa.head, fa.tail) - implicit def catsDataReducibleForNonEmptyList[A]: Reducible[NonEmptyList[?]] = - new NonEmptyReducible[NonEmptyList[?]] { - override def split[A](fa: NonEmptyList[A]): (A, F[A]) = (fa.head, fa.tail) - } + override def reduceLeft[A](fa: NonEmptyList[A])(f: (A, A) => A): A = + fa.reduceLeft(f) - implicit def catsDataMonadForNonEmptyList[A]: Monad[NonEmptyList[?]] = - new Monad[NonEmptyList[?]] { - override def map[A, B](fa: NonEmptyList[F, A])(f: A => B): NonEmptyList[F, B] = + override def map[A, B](fa: NonEmptyList[A])(f: A => B): NonEmptyList[B] = fa map f - def pure[A](x: A): NonEmptyList[F, A] = - NonEmptyList(x, Nil) - - // TODO Could we move this method (and other type class methods) to NonEmptyList and then reference them in the instances? I think that will make the scaladoc for NonEmptyList a bit nicer. @non - def flatMap[A, B](fa: NonEmptyList[F, A])(f: A => NonEmptyList[F, B]): NonEmptyList[F, B] = { - val end = monad.flatMap(fa.tail) { a => - val fa = f(a) - monad.combineK(monad.pure(fa.head), fa.tail) - } - val fst = f(fa.head) - NonEmptyList(fst.head, monad.combineK(fst.tail, end)) - // TODO @yilinwei https://github.com/typelevel/cats/pull/1120#discussion_r66882139 - // val xs = f(head) ++ tail.flatMap(f.andThen(_.toList)) - // NonEmptyList(xs.head, xs.tail) - } - } -} + 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 -trait NonEmptyListLowPriority0 { - implicit val nelComonad: Comonad[NonEmptyList[List, ?]] = - new Comonad[NonEmptyList[List, ?]] { - def coflatMap[A, B](fa: NonEmptyList[List, A])(f: NonEmptyList[List, A] => B): NonEmptyList[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(NonEmptyList(a, as))) + 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]] = + G.map2Eval(f(fa.head), Always(Traverse[List].traverse(fa.tail)(f)))(NonEmptyList(_, _)).value + + 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) } - NonEmptyList(f(fa), consume(fa.tail, ListBuffer.empty)) + go(f(a)) + NonEmptyList.fromListUnsafe(buf.result()) } - def extract[A](fa: NonEmptyList[List, A]): A = - fa.head + override def forall[A](fa: NonEmptyList[A])(p: A => Boolean): Boolean = + fa forall p - def map[A, B](fa: NonEmptyList[List, A])(f: A => B): NonEmptyList[List, B] = - fa map f - } -} + override def exists[A](fa: NonEmptyList[A])(p: A => Boolean): Boolean = + fa exists p -trait NonEmptyListLowPriority1 extends NonEmptyListLowPriority0 { - implicit def catsDataFunctorForNonEmptyList[F[_]](implicit F: Functor[F]): Functor[NonEmptyList[F, ?]] = - new Functor[NonEmptyList[F, ?]] { - def map[A, B](fa: NonEmptyList[F, A])(f: A => B): NonEmptyList[F, B] = - fa map f + override def toList[A](fa: NonEmptyList[A]): List[A] = fa.toList } + 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:Order]: Order[NonEmptyList[A]] = + Order.by(_.toList) } -trait NonEmptyListLowPriority2 extends NonEmptyListLowPriority1 { - implicit def catsDataTraverseForNonEmptyList[F[_]](implicit F: Traverse[F]): Traverse[NonEmptyList[F, ?]] = - new Traverse[NonEmptyList[F, ?]] { - def traverse[G[_], A, B](fa: NonEmptyList[F, A])(f: (A) => G[B])(implicit G: Applicative[G]): G[NonEmptyList[F, B]] = { - G.map2Eval(f(fa.head), Always(F.traverse(fa.tail)(f)))(NonEmptyList(_, _)).value - } +private[data] sealed trait NonEmptyListInstances0 extends NonEmptyListInstances1 { + implicit def catsDataPartialOrderForNonEmptyList[A:PartialOrder]: PartialOrder[NonEmptyList[A]] = + PartialOrder.by(_.toList) +} - def foldLeft[A, B](fa: NonEmptyList[F, A], b: B)(f: (B, A) => B): B = { - fa.foldLeft(b)(f) - } +private[data] sealed trait NonEmptyListInstances1 { - def foldRight[A, B](fa: NonEmptyList[F, A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = { - fa.foldRight(lb)(f) - } + implicit def catsDataEqForNonEmptyList[A](implicit A: Eq[A]): Eq[NonEmptyList[A]] = + new Eq[NonEmptyList[A]] { + def eqv(x: NonEmptyList[A], y: NonEmptyList[A]): Boolean = x === y } } - -object NonEmptyList extends NonEmptyListInstances 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/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 index df2d272d5a..7235ab1f91 100644 --- a/tests/src/test/scala/cats/tests/NonEmptyListTests.scala +++ b/tests/src/test/scala/cats/tests/NonEmptyListTests.scala @@ -3,7 +3,7 @@ package tests import cats.kernel.laws.{GroupLaws, OrderLaws} -import cats.data.{NonEmptyList, NonEmptyList} +import cats.data.NonEmptyList import cats.laws.discipline.{ComonadTests, FunctorTests, SemigroupKTests, FoldableTests, MonadTests, SerializableTests, CartesianTests, TraverseTests, ReducibleTests} import cats.laws.discipline.arbitrary._ @@ -12,41 +12,41 @@ class NonEmptyListTests extends CatsSuite { implicit override val generatorDrivenConfig: PropertyCheckConfiguration = PropertyCheckConfig(maxSize = 5, minSuccessful = 20) - checkAll("NonEmptyList[List, Int]", OrderLaws[NonEmptyList[List, Int]].eqv) + checkAll("NonEmptyList[Int]", OrderLaws[NonEmptyList[Int]].order) - checkAll("NonEmptyList[List, Int] with Option", TraverseTests[NonEmptyList[List, ?]].traverse[Int, Int, Int, Int, Option, Option]) - checkAll("Traverse[NonEmptyList[List, A]]", SerializableTests.serializable(Traverse[NonEmptyList[List, ?]])) + checkAll("NonEmptyList[Int] with Option", TraverseTests[NonEmptyList].traverse[Int, Int, Int, Int, Option, Option]) + checkAll("Traverse[NonEmptyList[A]]", SerializableTests.serializable(Traverse[NonEmptyList])) - checkAll("NonEmptyList[List, Int]", ReducibleTests[NonEmptyList[List, ?]].reducible[Option, Int, Int]) - checkAll("Reducible[NonEmptyList[List, ?]]", SerializableTests.serializable(Reducible[NonEmptyList[List, ?]])) + checkAll("NonEmptyList[Int]", ReducibleTests[NonEmptyList].reducible[Option, Int, Int]) + checkAll("Reducible[NonEmptyList]", SerializableTests.serializable(Reducible[NonEmptyList])) - implicit val iso = CartesianTests.Isomorphisms.invariant[NonEmptyList[ListWrapper, ?]](NonEmptyList.catsDataFunctorForNonEmptyList(ListWrapper.functor)) + //implicit val iso = CartesianTests.Isomorphisms.invariant[NonEmptyList](NonEmptyList.catsDataFunctorForNonEmptyList(ListWrapper.functor)) // Test instances that have more general constraints { implicit val monadCombine = ListWrapper.monadCombine - checkAll("NonEmptyList[ListWrapper, Int]", CartesianTests[NonEmptyList[ListWrapper, ?]].cartesian[Int, Int, Int]) - checkAll("Cartesian[NonEmptyList[ListWrapper, A]]", SerializableTests.serializable(Cartesian[NonEmptyList[ListWrapper, ?]])) + checkAll("NonEmptyList[Int]", CartesianTests[NonEmptyList].cartesian[Int, Int, Int]) + checkAll("Cartesian[NonEmptyList[A]]", SerializableTests.serializable(Cartesian[NonEmptyList])) } { implicit val functor = ListWrapper.functor - checkAll("NonEmptyList[ListWrapper, Int]", FunctorTests[NonEmptyList[ListWrapper, ?]].functor[Int, Int, Int]) - checkAll("Functor[NonEmptyList[ListWrapper, A]]", SerializableTests.serializable(Functor[NonEmptyList[ListWrapper, ?]])) + checkAll("NonEmptyList[Int]", FunctorTests[NonEmptyList].functor[Int, Int, Int]) + checkAll("Functor[NonEmptyList[A]]", SerializableTests.serializable(Functor[NonEmptyList])) } { implicit val monadCombine = ListWrapper.monadCombine - checkAll("NonEmptyList[ListWrapper, Int]", SemigroupKTests[NonEmptyList[ListWrapper, ?]].semigroupK[Int]) - checkAll("NonEmptyList[List, Int]", GroupLaws[NonEmptyList[List, Int]].semigroup) - checkAll("SemigroupK[NonEmptyList[ListWrapper, A]]", SerializableTests.serializable(SemigroupK[NonEmptyList[ListWrapper, ?]])) - checkAll("Semigroup[NonEmptyList[Int]]", SerializableTests.serializable(Semigroup[NonEmptyList[List, Int]])) + checkAll("NonEmptyList[Int]", SemigroupKTests[NonEmptyList].semigroupK[Int]) + checkAll("NonEmptyList[Int]", GroupLaws[NonEmptyList[Int]].semigroup) + checkAll("SemigroupK[NonEmptyList[A]]", SerializableTests.serializable(SemigroupK[NonEmptyList])) + checkAll("Semigroup[NonEmptyList[Int]]", SerializableTests.serializable(Semigroup[NonEmptyList[Int]])) } { implicit val foldable = ListWrapper.foldable - checkAll("NonEmptyList[ListWrapper, Int]", FoldableTests[NonEmptyList[ListWrapper, ?]].foldable[Int, Int]) - checkAll("Foldable[NonEmptyList[ListWrapper, A]]", SerializableTests.serializable(Foldable[NonEmptyList[ListWrapper, ?]])) + checkAll("NonEmptyList[Int]", FoldableTests[NonEmptyList].foldable[Int, Int]) + checkAll("Foldable[NonEmptyList[A]]", SerializableTests.serializable(Foldable[NonEmptyList])) } { @@ -56,7 +56,7 @@ class NonEmptyListTests extends CatsSuite { implicitly[Comonad[NonEmptyList]] } - implicit val iso2 = CartesianTests.Isomorphisms.invariant[NonEmptyList[List, ?]] + //implicit val iso2 = CartesianTests.Isomorphisms.invariant[NonEmptyList] checkAll("NonEmptyList[Int]", MonadTests[NonEmptyList].monad[Int, Int, Int]) checkAll("Monad[NonEmptyList[A]]", SerializableTests.serializable(Monad[NonEmptyList])) @@ -75,49 +75,49 @@ class NonEmptyListTests extends CatsSuite { test("Show is formatted correctly") { val nonEmptyList = NonEmptyList("Test", Nil) - nonEmptyList.show should === ("NonEmptyList(Test, List())") + nonEmptyList.show should === ("NonEmptyList(Test)") } - test("Creating NonEmptyList + unwrap is identity") { + test("Creating NonEmptyList + toList is identity") { forAll { (i: Int, tail: List[Int]) => val list = i :: tail val nonEmptyList = NonEmptyList(i, tail: _*) - list should === (nonEmptyList.unwrap) + list should === (nonEmptyList.toList) } } test("NonEmptyList#filter is consistent with List#filter") { forAll { (nel: NonEmptyList[Int], p: Int => Boolean) => - val list = nel.unwrap + 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.unwrap + 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.unwrap + 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.unwrap + 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.unwrap - nel.map(p).unwrap should === (list.map(p)) + val list = nel.toList + nel.map(p).toList should === (list.map(p)) } } 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) } From e33a0f0a78f9ce88a0fb7737b5a0f70a5664f71d Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Mon, 25 Jul 2016 11:15:06 -0400 Subject: [PATCH 08/11] Remove commented code --- tests/src/test/scala/cats/tests/NonEmptyListTests.scala | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/src/test/scala/cats/tests/NonEmptyListTests.scala b/tests/src/test/scala/cats/tests/NonEmptyListTests.scala index 7235ab1f91..7bbde15e2e 100644 --- a/tests/src/test/scala/cats/tests/NonEmptyListTests.scala +++ b/tests/src/test/scala/cats/tests/NonEmptyListTests.scala @@ -20,8 +20,6 @@ class NonEmptyListTests extends CatsSuite { checkAll("NonEmptyList[Int]", ReducibleTests[NonEmptyList].reducible[Option, Int, Int]) checkAll("Reducible[NonEmptyList]", SerializableTests.serializable(Reducible[NonEmptyList])) - //implicit val iso = CartesianTests.Isomorphisms.invariant[NonEmptyList](NonEmptyList.catsDataFunctorForNonEmptyList(ListWrapper.functor)) - // Test instances that have more general constraints { implicit val monadCombine = ListWrapper.monadCombine @@ -56,8 +54,6 @@ class NonEmptyListTests extends CatsSuite { implicitly[Comonad[NonEmptyList]] } - //implicit val iso2 = CartesianTests.Isomorphisms.invariant[NonEmptyList] - checkAll("NonEmptyList[Int]", MonadTests[NonEmptyList].monad[Int, Int, Int]) checkAll("Monad[NonEmptyList[A]]", SerializableTests.serializable(Monad[NonEmptyList])) From b1b4213fb7ed3b9c8ada8fb9b101d27e7c962780 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 26 Jul 2016 08:49:32 -0400 Subject: [PATCH 09/11] NonEmptyList improvements based on #1231 feedback And also unit test improvements --- core/src/main/scala/cats/Reducible.scala | 7 ++ .../main/scala/cats/data/NonEmptyList.scala | 96 +++++++++++++++---- .../scala/cats/tests/NonEmptyListTests.scala | 85 +++++++++------- .../cats/tests/NonEmptyVectorTests.scala | 7 ++ .../scala/cats/tests/ReducibleTests.scala | 20 ++++ 5 files changed, 165 insertions(+), 50 deletions(-) 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/data/NonEmptyList.scala b/core/src/main/scala/cats/data/NonEmptyList.scala index 730f34b3b2..c5e2611f44 100644 --- a/core/src/main/scala/cats/data/NonEmptyList.scala +++ b/core/src/main/scala/cats/data/NonEmptyList.scala @@ -2,7 +2,7 @@ package cats package data import cats.instances.list._ -import cats.syntax.eq._ +import cats.syntax.order._ import scala.annotation.tailrec import scala.collection.mutable.ListBuffer @@ -33,8 +33,11 @@ final case class NonEmptyList[A](head: A, tail: List[A]) { /** * remove elements not matching the predicate */ - def filter(p: A => Boolean): List[A] = - toList.filter(p) + def filter(p: A => Boolean): List[A] = { + val ftail = tail.filter(p) + if (p(head)) head :: ftail + else ftail + } /** * Append another NonEmptyList @@ -46,7 +49,8 @@ final case class NonEmptyList[A](head: A, tail: List[A]) { * Find the first element matching the predicate, if one exists */ def find(p: A => Boolean): Option[A] = - toList.find(p) + if (p(head)) Some(head) + else tail.find(p) /** * Check whether at least one element satisfies the predicate @@ -78,13 +82,19 @@ final case class NonEmptyList[A](head: A, tail: List[A]) { 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] = { - @tailrec def consume(as: List[A], buf: ListBuffer[B]): List[B] = + val buf = ListBuffer.empty[B] + @tailrec def consume(as: List[A]): List[B] = as match { case Nil => buf.toList - case a :: as => consume(as, buf += f(NonEmptyList(a, as))) + case a :: as => + buf += f(NonEmptyList(a, as)) + consume(as) } - NonEmptyList(f(this), consume(this.tail, ListBuffer.empty)) + NonEmptyList(f(this), consume(tail)) } def ===(o: NonEmptyList[A])(implicit A: Eq[A]): Boolean = @@ -99,12 +109,36 @@ final case class NonEmptyList[A](head: A, tail: List[A]) { 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]] = - if (l.isEmpty) None else Some(NonEmptyList(l.head, l.tail)) + 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] = - if (l.nonEmpty) NonEmptyList(l.head, l.tail) - else throw new IllegalArgumentException("Cannot create NonEmptyList from empty list") + 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 { @@ -136,8 +170,8 @@ private[data] sealed trait NonEmptyListInstances extends NonEmptyListInstances0 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]] = - G.map2Eval(f(fa.head), Always(Traverse[List].traverse(fa.tail)(f)))(NonEmptyList(_, _)).value + 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) @@ -167,6 +201,8 @@ private[data] sealed trait NonEmptyListInstances extends NonEmptyListInstances0 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]] = @@ -175,19 +211,43 @@ private[data] sealed trait NonEmptyListInstances extends NonEmptyListInstances0 implicit def catsDataSemigroupForNonEmptyList[A]: Semigroup[NonEmptyList[A]] = SemigroupK[NonEmptyList].algebra[A] - implicit def catsDataOrderForNonEmptyList[A:Order]: Order[NonEmptyList[A]] = - Order.by(_.toList) + 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:PartialOrder]: PartialOrder[NonEmptyList[A]] = - PartialOrder.by(_.toList) + 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 Eq[NonEmptyList[A]] { - def eqv(x: NonEmptyList[A], y: NonEmptyList[A]): Boolean = x === y + 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/tests/src/test/scala/cats/tests/NonEmptyListTests.scala b/tests/src/test/scala/cats/tests/NonEmptyListTests.scala index 7bbde15e2e..3a1b5d76ad 100644 --- a/tests/src/test/scala/cats/tests/NonEmptyListTests.scala +++ b/tests/src/test/scala/cats/tests/NonEmptyListTests.scala @@ -4,7 +4,7 @@ package tests import cats.kernel.laws.{GroupLaws, OrderLaws} import cats.data.NonEmptyList -import cats.laws.discipline.{ComonadTests, FunctorTests, SemigroupKTests, FoldableTests, MonadTests, SerializableTests, CartesianTests, TraverseTests, ReducibleTests} +import cats.laws.discipline.{ComonadTests, SemigroupKTests, MonadRecTests, SerializableTests, TraverseTests, ReducibleTests} import cats.laws.discipline.arbitrary._ class NonEmptyListTests extends CatsSuite { @@ -20,45 +20,37 @@ class NonEmptyListTests extends CatsSuite { checkAll("NonEmptyList[Int]", ReducibleTests[NonEmptyList].reducible[Option, Int, Int]) checkAll("Reducible[NonEmptyList]", SerializableTests.serializable(Reducible[NonEmptyList])) - // Test instances that have more general constraints - { - implicit val monadCombine = ListWrapper.monadCombine - checkAll("NonEmptyList[Int]", CartesianTests[NonEmptyList].cartesian[Int, Int, Int]) - checkAll("Cartesian[NonEmptyList[A]]", SerializableTests.serializable(Cartesian[NonEmptyList])) - } + checkAll("NonEmptyList[Int]", MonadRecTests[NonEmptyList].monadRec[Int, Int, Int]) + checkAll("Monad[NonEmptyList[A]]", SerializableTests.serializable(Monad[NonEmptyList])) - { - implicit val functor = ListWrapper.functor - checkAll("NonEmptyList[Int]", FunctorTests[NonEmptyList].functor[Int, Int, Int]) - checkAll("Functor[NonEmptyList[A]]", SerializableTests.serializable(Functor[NonEmptyList])) - } + checkAll("NonEmptyList[Int]", SemigroupKTests[NonEmptyList].semigroupK[Int]) + checkAll("SemigroupK[NonEmptyList[A]]", SerializableTests.serializable(SemigroupK[NonEmptyList])) - { - implicit val monadCombine = ListWrapper.monadCombine - checkAll("NonEmptyList[Int]", SemigroupKTests[NonEmptyList].semigroupK[Int]) - checkAll("NonEmptyList[Int]", GroupLaws[NonEmptyList[Int]].semigroup) - checkAll("SemigroupK[NonEmptyList[A]]", SerializableTests.serializable(SemigroupK[NonEmptyList])) - checkAll("Semigroup[NonEmptyList[Int]]", SerializableTests.serializable(Semigroup[NonEmptyList[Int]])) - } + checkAll("NonEmptyList[Int]", GroupLaws[NonEmptyList[Int]].semigroup) + checkAll("Semigroup[NonEmptyList[Int]]", SerializableTests.serializable(Semigroup[NonEmptyList[Int]])) - { - implicit val foldable = ListWrapper.foldable - checkAll("NonEmptyList[Int]", FoldableTests[NonEmptyList].foldable[Int, Int]) - checkAll("Foldable[NonEmptyList[A]]", SerializableTests.serializable(Foldable[NonEmptyList])) - } + 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]]])) { - // Test functor and subclasses don't have implicit conflicts - implicitly[Functor[NonEmptyList]] - implicitly[Monad[NonEmptyList]] - implicitly[Comonad[NonEmptyList]] + 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]]] } - checkAll("NonEmptyList[Int]", MonadTests[NonEmptyList].monad[Int, Int, Int]) - checkAll("Monad[NonEmptyList[A]]", SerializableTests.serializable(Monad[NonEmptyList])) + { + 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]]])) - checkAll("NonEmptyList[Int]", ComonadTests[NonEmptyList].comonad[Int, Int, Int]) - checkAll("Comonad[NonEmptyList[A]]", SerializableTests.serializable(Comonad[NonEmptyList])) + Eq[NonEmptyList[ListWrapper[Int]]] + PartialOrder[NonEmptyList[ListWrapper[Int]]] + } test("Show is not empty and is formatted as expected") { forAll { (nel: NonEmptyList[Int]) => @@ -158,4 +150,33 @@ class NonEmptyListTests extends CatsSuite { 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]) + } + } +} + +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] = + NonEmptyList(start, (start + 1L).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..035c30b034 100644 --- a/tests/src/test/scala/cats/tests/NonEmptyVectorTests.scala +++ b/tests/src/test/scala/cats/tests/NonEmptyVectorTests.scala @@ -219,3 +219,10 @@ 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] = + NonEmptyVector(start, (start + 1L).to(endInclusive).toVector) +} 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)) + } + } +} From c8fcaf068de98bf4e726ced140c4fbedfe75bc9f Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 26 Jul 2016 09:43:29 -0400 Subject: [PATCH 10/11] Work around implicit widening error in 2.10 --- tests/src/test/scala/cats/tests/NonEmptyListTests.scala | 9 +++++++-- .../src/test/scala/cats/tests/NonEmptyVectorTests.scala | 8 ++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/tests/src/test/scala/cats/tests/NonEmptyListTests.scala b/tests/src/test/scala/cats/tests/NonEmptyListTests.scala index 3a1b5d76ad..b1f48a320e 100644 --- a/tests/src/test/scala/cats/tests/NonEmptyListTests.scala +++ b/tests/src/test/scala/cats/tests/NonEmptyListTests.scala @@ -177,6 +177,11 @@ class NonEmptyListTests extends CatsSuite { 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] = - NonEmptyList(start, (start + 1L).to(endInclusive).toList) + 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 035c30b034..5fd419252f 100644 --- a/tests/src/test/scala/cats/tests/NonEmptyVectorTests.scala +++ b/tests/src/test/scala/cats/tests/NonEmptyVectorTests.scala @@ -223,6 +223,10 @@ 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] = - NonEmptyVector(start, (start + 1L).to(endInclusive).toVector) + 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) + } } From cfdd085189defe565a1fd3690795326634eae7cb Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 26 Jul 2016 20:18:23 -0400 Subject: [PATCH 11/11] Add :: method to NonEmptyList --- core/src/main/scala/cats/data/NonEmptyList.scala | 2 ++ tests/src/test/scala/cats/tests/NonEmptyListTests.scala | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/core/src/main/scala/cats/data/NonEmptyList.scala b/core/src/main/scala/cats/data/NonEmptyList.scala index c5e2611f44..cebb6a7c27 100644 --- a/core/src/main/scala/cats/data/NonEmptyList.scala +++ b/core/src/main/scala/cats/data/NonEmptyList.scala @@ -30,6 +30,8 @@ final case class NonEmptyList[A](head: A, tail: List[A]) { 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 */ diff --git a/tests/src/test/scala/cats/tests/NonEmptyListTests.scala b/tests/src/test/scala/cats/tests/NonEmptyListTests.scala index b1f48a320e..a12bd35ec1 100644 --- a/tests/src/test/scala/cats/tests/NonEmptyListTests.scala +++ b/tests/src/test/scala/cats/tests/NonEmptyListTests.scala @@ -172,6 +172,12 @@ class NonEmptyListTests extends CatsSuite { 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") {