Skip to content

Commit

Permalink
Sync NEL and NEV methods (fixes #1832) (#1838)
Browse files Browse the repository at this point in the history
* Sync NEL and NEV methods (fixes #1832)

* Override Traverse#zipWithIndex for NonEmptyVector

* More tests for NonEmptyVector#zipWithIndex

* Add deprecated overload of NonEmptyList#concat

* Try to appease CI

* Deprecating separately
  • Loading branch information
durban authored and kailuowang committed Oct 17, 2017
1 parent d7a0204 commit 8d94237
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 11 deletions.
28 changes: 20 additions & 8 deletions core/src/main/scala/cats/data/NonEmptyList.scala
Original file line number Diff line number Diff line change
Expand Up @@ -66,19 +66,37 @@ 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
*/
def map[B](f: A => B): NonEmptyList[B] =
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)

/**
Expand Down Expand Up @@ -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
*/
Expand Down Expand Up @@ -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)

Expand Down
21 changes: 21 additions & 0 deletions core/src/main/scala/cats/data/NonEmptyVector.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand All @@ -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]]
*/
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)

Expand Down
58 changes: 57 additions & 1 deletion tests/src/test/scala/cats/tests/NonEmptyVectorTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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]) =>

Expand All @@ -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") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down Expand Up @@ -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)
}
}

Expand All @@ -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))
}
Expand Down Expand Up @@ -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

Expand Down

0 comments on commit 8d94237

Please sign in to comment.