diff --git a/core/src/main/scala/cats/data/NonEmptyList.scala b/core/src/main/scala/cats/data/NonEmptyList.scala index aff19f3a8c..8bca519413 100644 --- a/core/src/main/scala/cats/data/NonEmptyList.scala +++ b/core/src/main/scala/cats/data/NonEmptyList.scala @@ -133,6 +133,37 @@ final case class NonEmptyList[+A](head: A, tail: List[A]) extends NonEmptyCollec def prepend[AA >: A](a: AA): NonEmptyList[AA] = NonEmptyList(a, head :: tail) + /** + * Alias for [[prependList]] + * + * {{{ + * scala> import cats.data.NonEmptyList + * scala> val nel = NonEmptyList.of(1, 2, 3) + * scala> val list = List(-1, 0) + * scala> list ++: nel + * res0: cats.data.NonEmptyList[Int] = NonEmptyList(-1, 0, 1, 2, 3) + * }}} + */ + def ++:[AA >: A](other: List[AA]): NonEmptyList[AA] = + prependList(other) + + /** + * Prepend another `List` + * + * {{{ + * scala> import cats.data.NonEmptyList + * scala> val nel = NonEmptyList.of(1, 2, 3) + * scala> val list = List(-1, 0) + * scala> nel.prependList(list) + * res0: cats.data.NonEmptyList[Int] = NonEmptyList(-1, 0, 1, 2, 3) + * }}} + */ + def prependList[AA >: A](other: List[AA]): NonEmptyList[AA] = + other match { + case Nil => this + case head :: tail => NonEmptyList(head, tail ::: toList) + } + /** * Alias for append * @@ -149,6 +180,19 @@ final case class NonEmptyList[+A](head: A, tail: List[A]) extends NonEmptyCollec def append[AA >: A](a: AA): NonEmptyList[AA] = NonEmptyList(head, tail :+ a) + /** + * Alias for [[concat]] + * + * {{{ + * scala> import cats.data.NonEmptyList + * scala> val nel = NonEmptyList.of(1, 2, 3) + * scala> nel.appendList(List(4, 5)) + * res0: cats.data.NonEmptyList[Int] = NonEmptyList(1, 2, 3, 4, 5) + * }}} + */ + def appendList[AA >: A](other: List[AA]): NonEmptyList[AA] = + concat(other) + /** * Alias for concatNel * diff --git a/core/src/main/scala/cats/data/NonEmptySeq.scala b/core/src/main/scala/cats/data/NonEmptySeq.scala index 38b88d8c17..03a2e14078 100644 --- a/core/src/main/scala/cats/data/NonEmptySeq.scala +++ b/core/src/main/scala/cats/data/NonEmptySeq.scala @@ -119,6 +119,11 @@ final class NonEmptySeq[+A] private (val toSeq: Seq[A]) extends AnyVal with NonE */ def concat[AA >: A](other: Seq[AA]): NonEmptySeq[AA] = new NonEmptySeq(toSeq ++ other) + /** + * Append another `Seq` to this, producing a new `NonEmptySeq`. + */ + def appendSeq[AA >: A](other: Seq[AA]): NonEmptySeq[AA] = concat(other) + /** * Append another `NonEmptySeq` to this, producing a new `NonEmptySeq`. */ diff --git a/core/src/main/scala/cats/data/NonEmptyVector.scala b/core/src/main/scala/cats/data/NonEmptyVector.scala index ec8849e172..c74a0c29c3 100644 --- a/core/src/main/scala/cats/data/NonEmptyVector.scala +++ b/core/src/main/scala/cats/data/NonEmptyVector.scala @@ -122,6 +122,18 @@ final class NonEmptyVector[+A] private (val toVector: Vector[A]) */ def concat[AA >: A](other: Vector[AA]): NonEmptyVector[AA] = new NonEmptyVector(toVector ++ other) + /** + * Append another `Vector` to this, producing a new `NonEmptyVector` + * + * {{{ + * scala> import cats.data.NonEmptyVector + * scala> val nev = NonEmptyVector.of(1, 2, 3) + * scala> nev.appendVector(Vector(4, 5)) + * res0: cats.data.NonEmptyVector[Int] = NonEmptyVector(1, 2, 3, 4, 5) + * }}} + */ + def appendVector[AA >: A](other: Vector[AA]): NonEmptyVector[AA] = concat(other) + /** * Append another `NonEmptyVector` to this, producing a new `NonEmptyVector`. */ diff --git a/tests/shared/src/test/scala/cats/tests/NonEmptyListSuite.scala b/tests/shared/src/test/scala/cats/tests/NonEmptyListSuite.scala index 6235ba2fb0..3fd4a455ae 100644 --- a/tests/shared/src/test/scala/cats/tests/NonEmptyListSuite.scala +++ b/tests/shared/src/test/scala/cats/tests/NonEmptyListSuite.scala @@ -285,6 +285,13 @@ class NonEmptyListSuite extends NonEmptyCollectionSuite[List, NonEmptyList, NonE } } + test("++: consistent with List#:::") { + forAll { (nel: NonEmptyList[Int], i: List[Int]) => + assert((i ++: nel).toList === (i ::: nel.toList)) + assert(nel.prependList(i).toList === (i ::: nel.toList)) + } + } + test("NonEmptyList#distinct is consistent with List#distinct") { forAll { (nel: NonEmptyList[Int]) => assert(nel.distinct.toList === (nel.toList.distinct)) @@ -378,6 +385,7 @@ class NonEmptyListSuite extends NonEmptyCollectionSuite[List, NonEmptyList, NonE forAll { (nel: NonEmptyList[Int], l: List[Int], n: Int) => assert((nel ++ l).toList === (nel.toList ::: l)) assert(nel.concat(l).toList === (nel.toList ::: l)) + assert(nel.appendList(l).toList === (nel.toList ::: l)) assert(nel.concatNel(NonEmptyList(n, l)).toList === (nel.toList ::: (n :: l))) } } diff --git a/tests/shared/src/test/scala/cats/tests/NonEmptySeqSuite.scala b/tests/shared/src/test/scala/cats/tests/NonEmptySeqSuite.scala new file mode 100644 index 0000000000..95a0523751 --- /dev/null +++ b/tests/shared/src/test/scala/cats/tests/NonEmptySeqSuite.scala @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2015 Typelevel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package cats.tests + +import cats.data.NonEmptySeq +import cats.laws.discipline.arbitrary._ +import cats.syntax.seq._ +import org.scalacheck.Prop._ + +import scala.collection.immutable.Seq + +class NonEmptySeqSuite extends NonEmptyCollectionSuite[Seq, NonEmptySeq, NonEmptySeq] { + protected def toList[A](value: NonEmptySeq[A]): List[A] = value.toSeq.toList + protected def underlyingToList[A](underlying: Seq[A]): List[A] = underlying.toList + protected def toNonEmptyCollection[A](value: NonEmptySeq[A]): NonEmptySeq[A] = value + + test("neSeq => Seq => neSeq returns original neSeq")( + forAll { (fa: NonEmptySeq[Int]) => + assert(fa.toSeq.toNeSeq == Some(fa)) + } + ) + + test("NonEmptySeq#concat/appendSeq is consistent with Seq#++")( + forAll { (fa: NonEmptySeq[Int], fb: Seq[Int]) => + assert((fa ++ fb).toSeq == fa.toSeq ++ fb) + assert(fa.concat(fb).toSeq == fa.toSeq ++ fb) + assert(fa.appendSeq(fb).toSeq == fa.toSeq ++ fb) + } + ) + + test("NonEmptySeq#concatNeSeq is consistent with concat")( + forAll { (fa: NonEmptySeq[Int], fb: NonEmptySeq[Int]) => + assert(fa.concatNeSeq(fb).toSeq == fa.concat(fb.toSeq).toSeq) + } + ) + + test("toNeSeq on empty Seq returns None") { + assert(Seq.empty[Int].toNeSeq == None) + } +} diff --git a/tests/shared/src/test/scala/cats/tests/NonEmptyVectorSuite.scala b/tests/shared/src/test/scala/cats/tests/NonEmptyVectorSuite.scala index 79a2d3c0f7..21775f5d01 100644 --- a/tests/shared/src/test/scala/cats/tests/NonEmptyVectorSuite.scala +++ b/tests/shared/src/test/scala/cats/tests/NonEmptyVectorSuite.scala @@ -256,6 +256,12 @@ class NonEmptyVectorSuite extends NonEmptyCollectionSuite[Vector, NonEmptyVector } } + test("appendVector is consistent with concat") { + forAll { (nonEmptyVector: NonEmptyVector[Int], vector: Vector[Int]) => + assert(nonEmptyVector.appendVector(vector) === nonEmptyVector.concat(vector)) + } + } + test(":+ is consistent with concat") { forAll { (nonEmptyVector: NonEmptyVector[Int], i: Int) => assert(nonEmptyVector :+ i === (nonEmptyVector.concat(Vector(i)))) diff --git a/tests/shared/src/test/scala/cats/tests/SeqSuite.scala b/tests/shared/src/test/scala/cats/tests/SeqSuite.scala index 1312ea059b..e2d7106b5a 100644 --- a/tests/shared/src/test/scala/cats/tests/SeqSuite.scala +++ b/tests/shared/src/test/scala/cats/tests/SeqSuite.scala @@ -22,7 +22,7 @@ package cats.tests import cats.{Align, Alternative, CoflatMap, Monad, Semigroupal, Traverse, TraverseFilter} -import cats.data.{NonEmptySeq, ZipSeq} +import cats.data.ZipSeq import cats.laws.discipline.{ AlignTests, AlternativeTests, @@ -37,7 +37,6 @@ import cats.laws.discipline.{ } import cats.laws.discipline.arbitrary._ import cats.syntax.show._ -import cats.syntax.seq._ import cats.syntax.eq._ import org.scalacheck.Prop._ import scala.collection.immutable.Seq @@ -75,16 +74,6 @@ class SeqSuite extends CatsSuite { } } - test("neSeq => Seq => neSeq returns original neSeq")( - forAll { (fa: NonEmptySeq[Int]) => - assert(fa.toSeq.toNeSeq == Some(fa)) - } - ) - - test("toNeSeq on empty Seq returns None") { - assert(Seq.empty[Int].toNeSeq == None) - } - test("traverse is stack-safe") { val seq = (0 until 100000).toSeq val sumAll = Traverse[Seq]