diff --git a/core/src/main/scala/cats/data/NonEmptyList.scala b/core/src/main/scala/cats/data/NonEmptyList.scala index 4dfc37dc61..946729b011 100644 --- a/core/src/main/scala/cats/data/NonEmptyList.scala +++ b/core/src/main/scala/cats/data/NonEmptyList.scala @@ -66,6 +66,8 @@ final case class NonEmptyList[+A](head: A, tail: List[A]) { */ def size: Int = 1 + tail.size + def length: Int = size + /** * Applies f to all the elements of the structure */ @@ -73,12 +75,28 @@ final case class NonEmptyList[+A](head: A, tail: List[A]) { NonEmptyList(f(head), tail.map(f)) def ++[AA >: A](l: List[AA]): NonEmptyList[AA] = - NonEmptyList(head, tail ++ l) + concat(l) + + def concat[AA >: A](other: List[AA]): NonEmptyList[AA] = + NonEmptyList(head, tail ::: other) + + @deprecated("Use concatNel", since = "1.0.0-RC1") + def concat[AA >: A](other: NonEmptyList[AA]): NonEmptyList[AA] = + concatNel(other) + + /** + * Append another NonEmptyList + */ + def concatNel[AA >: A](other: NonEmptyList[AA]): NonEmptyList[AA] = + NonEmptyList(head, tail ::: other.toList) def flatMap[B](f: A => NonEmptyList[B]): NonEmptyList[B] = f(head) ++ tail.flatMap(f andThen (_.toList)) def ::[AA >: A](a: AA): NonEmptyList[AA] = + prepend(a) + + def prepend[AA >: A](a: AA): NonEmptyList[AA] = NonEmptyList(a, head :: tail) /** @@ -137,12 +155,6 @@ final case class NonEmptyList[+A](head: A, tail: List[A]) { } } - /** - * Append another NonEmptyList - */ - def concat[AA >: A](other: NonEmptyList[AA]): NonEmptyList[AA] = - NonEmptyList(head, tail ::: other.toList) - /** * Find the first element matching the predicate, if one exists */ @@ -405,7 +417,7 @@ private[data] sealed abstract class NonEmptyListInstances extends NonEmptyListIn with Monad[NonEmptyList] with NonEmptyTraverse[NonEmptyList] { def combineK[A](a: NonEmptyList[A], b: NonEmptyList[A]): NonEmptyList[A] = - a concat b + a concatNel b override def split[A](fa: NonEmptyList[A]): (A, List[A]) = (fa.head, fa.tail) diff --git a/core/src/main/scala/cats/data/NonEmptyVector.scala b/core/src/main/scala/cats/data/NonEmptyVector.scala index 9fdeebd230..0750618411 100644 --- a/core/src/main/scala/cats/data/NonEmptyVector.scala +++ b/core/src/main/scala/cats/data/NonEmptyVector.scala @@ -36,6 +36,10 @@ final class NonEmptyVector[+A] private (val toVector: Vector[A]) extends AnyVal def tail: Vector[A] = toVector.tail + def last: A = toVector.last + + def init: Vector[A] = toVector.init + /** * Remove elements not matching the predicate * @@ -60,6 +64,8 @@ final class NonEmptyVector[+A] private (val toVector: Vector[A]) extends AnyVal */ def filterNot(f: A => Boolean): Vector[A] = toVector.filterNot(f) + def collect[B](pf: PartialFunction[A, B]): Vector[B] = toVector.collect(pf) + /** * Alias for [[concat]] */ @@ -198,6 +204,18 @@ final class NonEmptyVector[+A] private (val toVector: Vector[A]) extends AnyVal */ def zipWith[B, C](b: NonEmptyVector[B])(f: (A, B) => C): NonEmptyVector[C] = NonEmptyVector.fromVectorUnsafe((toVector, b.toVector).zipped.map(f)) + + def reverse: NonEmptyVector[A] = + new NonEmptyVector(toVector.reverse) + + def zipWithIndex: NonEmptyVector[(A, Int)] = + new NonEmptyVector(toVector.zipWithIndex) + + def sortBy[B](f: A => B)(implicit B: Order[B]): NonEmptyVector[A] = + new NonEmptyVector(toVector.sortBy(f)(B.toOrdering)) + + def sorted[AA >: A](implicit AA: Order[AA]): NonEmptyVector[AA] = + new NonEmptyVector(toVector.sorted(AA.toOrdering)) } private[data] sealed abstract class NonEmptyVectorInstances { @@ -251,6 +269,9 @@ private[data] sealed abstract class NonEmptyVectorInstances { override def traverse[G[_], A, B](fa: NonEmptyVector[A])(f: (A) => G[B])(implicit G: Applicative[G]): G[NonEmptyVector[B]] = G.map2Eval(f(fa.head), Always(Traverse[Vector].traverse(fa.tail)(f)))(NonEmptyVector(_, _)).value + override def zipWithIndex[A](fa: NonEmptyVector[A]): NonEmptyVector[(A, Int)] = + fa.zipWithIndex + override def foldLeft[A, B](fa: NonEmptyVector[A], b: B)(f: (B, A) => B): B = fa.foldLeft(b)(f) diff --git a/tests/src/test/scala/cats/tests/NonEmptyVectorTests.scala b/tests/src/test/scala/cats/tests/NonEmptyVectorTests.scala index 93b3284d34..7bd91690d0 100644 --- a/tests/src/test/scala/cats/tests/NonEmptyVectorTests.scala +++ b/tests/src/test/scala/cats/tests/NonEmptyVectorTests.scala @@ -317,12 +317,19 @@ class NonEmptyVectorTests extends CatsSuite { } } - test("NonEmptyVector#zipWith is consistent with Vector#zip and then Vector#map") { forAll { (a: NonEmptyVector[Int], b: NonEmptyVector[Int], f: (Int, Int) => Int) => a.zipWith(b)(f).toVector should ===(a.toVector.zip(b.toVector).map { case (x, y) => f(x, y) }) } } + + test("NonEmptyVector#zipWith is consistent with #zipWithIndex") { + forAll { nev: NonEmptyVector[Int] => + val zw = nev.zipWith(NonEmptyVector.fromVectorUnsafe((0 until nev.length).toVector))(Tuple2.apply) + nev.zipWithIndex should === (zw) + } + } + test("NonEmptyVector#nonEmptyPartition remains sorted") { forAll { (nev: NonEmptyVector[Int], f: Int => Either[String, String]) => @@ -333,6 +340,55 @@ class NonEmptyVectorTests extends CatsSuite { ior.right.map(xs => xs.sorted should === (xs)) } } + + test("NonEmptyVector#last is consistent with Vector#last") { + forAll { nonEmptyVector: NonEmptyVector[Int] => + nonEmptyVector.last should === (nonEmptyVector.toVector.last) + } + } + + test("NonEmptyVector#init is consistent with Vector#init") { + forAll { nonEmptyVector: NonEmptyVector[Int] => + nonEmptyVector.init should === (nonEmptyVector.toVector.init) + } + } + + test("NonEmptyVector#collect is consistent with Vector#collect") { + val pf: PartialFunction[Int, Double] = { + case i if (i % 2 == 0) => i.toDouble + } + forAll { nonEmptyVector: NonEmptyVector[Int] => + nonEmptyVector.collect(pf) should === (nonEmptyVector.toVector.collect(pf)) + } + } + + test("NonEmptyVector#length and size is consistent with Vector#length") { + forAll { nonEmptyVector: NonEmptyVector[Int] => + nonEmptyVector.length should === (nonEmptyVector.toVector.length) + nonEmptyVector.size should === (nonEmptyVector.toVector.length.toLong) + } + } + + test("NonEmptyVector#reverse is consistent with Vector#reverse") { + forAll { nonEmptyVector: NonEmptyVector[Int] => + nonEmptyVector.reverse should === (NonEmptyVector.fromVectorUnsafe(nonEmptyVector.toVector.reverse)) + } + } + + test("NonEmptyVector#zipWithIndex is consistent with Vector#zipWithIndex") { + forAll { nonEmptyVector: NonEmptyVector[Int] => + val expected = NonEmptyVector.fromVectorUnsafe(nonEmptyVector.toVector.zipWithIndex) + nonEmptyVector.zipWithIndex should === (expected) + Traverse[NonEmptyVector].zipWithIndex(nonEmptyVector) should === (expected) + } + } + + test("NonEmptyVector#sorted and sortBy is consistent with Vector#sorted and sortBy") { + forAll { nonEmptyVector: NonEmptyVector[Int] => + nonEmptyVector.sorted should === (NonEmptyVector.fromVectorUnsafe(nonEmptyVector.toVector.sorted)) + nonEmptyVector.sortBy(i => -i) should === (NonEmptyVector.fromVectorUnsafe(nonEmptyVector.toVector.sortBy(i => -i))) + } + } } class ReducibleNonEmptyVectorCheck extends ReducibleCheck[NonEmptyVector]("NonEmptyVector") { diff --git a/tests/src/test/scala/cats/tests/NonEmptyListTests.scala b/tests/src/test/scala/cats/tests/nonEmptyListTests.scala similarity index 92% rename from tests/src/test/scala/cats/tests/NonEmptyListTests.scala rename to tests/src/test/scala/cats/tests/nonEmptyListTests.scala index 2e1497dcc0..225f7a5f0e 100644 --- a/tests/src/test/scala/cats/tests/NonEmptyListTests.scala +++ b/tests/src/test/scala/cats/tests/nonEmptyListTests.scala @@ -224,6 +224,7 @@ class NonEmptyListTests extends CatsSuite { test(":: consistent with List") { forAll { (nel: NonEmptyList[Int], i: Int) => (i :: nel).toList should === (i :: nel.toList) + nel.prepend(i).toList should === (i :: nel.toList) } } @@ -257,9 +258,10 @@ class NonEmptyListTests extends CatsSuite { } } - test("NonEmptyList#size is consistent with List#size") { + test("NonEmptyList#size and length is consistent with List#size") { forAll { nel: NonEmptyList[Int] => nel.size should === (nel.toList.size) + nel.length should === (nel.toList.size) } } @@ -282,7 +284,15 @@ class NonEmptyListTests extends CatsSuite { } } - test("NonEmptyList#fromFoldable is consistent with NonEmptyList#fromList") { + test("NonEmptyList#concat/concatNel is consistent with List#:::") { + forAll { (nel: NonEmptyList[Int], l: List[Int], n: Int) => + (nel ++ l).toList should === (nel.toList ::: l) + nel.concat(l).toList should === (nel.toList ::: l) + nel.concatNel(NonEmptyList(n, l)).toList should === (nel.toList ::: (n :: l)) + } + } + + test("NonEmptyList#fromFoldabale is consistent with NonEmptyList#fromList") { forAll { (xs: List[Int]) => NonEmptyList.fromList(xs) should === (NonEmptyList.fromFoldable(xs)) } @@ -312,6 +322,16 @@ class NonEmptyListTests extends CatsSuite { } } +@deprecated("to be able to test deprecated methods", since = "1.0.0-RC1") +class DeprecatedNonEmptyListTests extends CatsSuite { + + test("Deprecated NonEmptyList#concat is consistent with List#:::") { + forAll { (nel: NonEmptyList[Int], l: List[Int], n: Int) => + nel.concat(NonEmptyList(n, l)).toList should === (nel.toList ::: (n :: l)) + } + } +} + class ReducibleNonEmptyListCheck extends ReducibleCheck[NonEmptyList]("NonEmptyList") { def iterator[T](nel: NonEmptyList[T]): Iterator[T] = nel.toList.iterator