Skip to content

Commit

Permalink
Merge branch 'main' into sam-instances
Browse files Browse the repository at this point in the history
  • Loading branch information
joroKr21 committed Aug 25, 2022
2 parents 132b4c3 + 3c98865 commit ab74f33
Show file tree
Hide file tree
Showing 18 changed files with 204 additions and 22 deletions.
4 changes: 2 additions & 2 deletions core/src/main/scala/cats/ApplicativeError.scala
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ trait ApplicativeError[F[_], E] extends Applicative[F] {
def catchNonFatal[A](a: => A)(implicit ev: Throwable <:< E): F[A] =
try pure(a)
catch {
case NonFatal(e) => raiseError(e)
case e if NonFatal(e) => raiseError(e)
}

/**
Expand All @@ -257,7 +257,7 @@ trait ApplicativeError[F[_], E] extends Applicative[F] {
def catchNonFatalEval[A](a: Eval[A])(implicit ev: Throwable <:< E): F[A] =
try pure(a.value)
catch {
case NonFatal(e) => raiseError(e)
case e if NonFatal(e) => raiseError(e)
}

/**
Expand Down
16 changes: 16 additions & 0 deletions core/src/main/scala/cats/TraverseFilter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,22 @@ trait TraverseFilter[F[_]] extends FunctorFilter[F] {
*/
def traverseFilter[G[_], A, B](fa: F[A])(f: A => G[Option[B]])(implicit G: Applicative[G]): G[F[B]]

/**
* A combined [[traverse]] and [[collect]].
*
* scala> import cats.implicits._
* scala> val m: Map[Int, String] = Map(1 -> "one", 2 -> "two")
* scala> val l: List[Int] = List(1, 2, 3, 4)
* scala> def asString: PartialFunction[Int, Eval[Option[String]]] = { case n if n % 2 == 0 => Now(m.get(n)) }
* scala> val result: Eval[List[Option[String]]] = l.traverseCollect(asString)
* scala> result.value
* res0: List[Option[String]] = List(Some(two), None)
*/
def traverseCollect[G[_], A, B](fa: F[A])(f: PartialFunction[A, G[B]])(implicit G: Applicative[G]): G[F[B]] = {
val optF = f.lift
traverseFilter(fa)(a => Traverse[Option].sequence(optF(a)))
}

/**
* {{{
* scala> import cats.implicits._
Expand Down
44 changes: 44 additions & 0 deletions core/src/main/scala/cats/data/NonEmptyList.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand All @@ -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
*
Expand Down
5 changes: 5 additions & 0 deletions core/src/main/scala/cats/data/NonEmptySeq.scala
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,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`.
*/
Expand Down
12 changes: 12 additions & 0 deletions core/src/main/scala/cats/data/NonEmptyVector.scala
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,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`.
*/
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/scala/cats/data/Validated.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1125,7 +1125,7 @@ private[data] trait ValidatedFunctions {
try {
valid(f)
} catch {
case scala.util.control.NonFatal(t) => invalid(t)
case t if scala.util.control.NonFatal(t) => invalid(t)
}

/**
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/scala/cats/instances/try.scala
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ trait TryInstances extends TryInstances1 {
ta match {
case Success(a) => bind(a); case Failure(e) => recover(e)
}
catch { case NonFatal(e) => Failure(e) }
catch { case e if NonFatal(e) => Failure(e) }

override def recover[A](ta: Try[A])(pf: PartialFunction[Throwable, A]): Try[A] =
ta.recover(pf)
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/scala/cats/syntax/either.scala
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ final class EitherObjectOps(private val either: Either.type) extends AnyVal {
try {
right(f)
} catch {
case scala.util.control.NonFatal(t) => left(t)
case t if scala.util.control.NonFatal(t) => left(t)
}

/**
Expand Down
12 changes: 12 additions & 0 deletions core/src/main/scala/cats/syntax/traverseFilter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ trait TraverseFilterSyntax extends TraverseFilter.ToTraverseFilterOps
private[syntax] trait TraverseFilterSyntaxBinCompat0 {
implicit def toSequenceFilterOps[F[_], G[_], A](fgoa: F[G[Option[A]]]): SequenceFilterOps[F, G, A] =
new SequenceFilterOps(fgoa)

implicit def toTraverseFilterOps[F[_], G[_], A](fa: F[A]): TraverseFilterOps[F, G, A] =
new TraverseFilterOps(fa)
}

final class SequenceFilterOps[F[_], G[_], A](private val fgoa: F[G[Option[A]]]) extends AnyVal {
Expand All @@ -41,3 +44,12 @@ final class SequenceFilterOps[F[_], G[_], A](private val fgoa: F[G[Option[A]]])
*/
def sequenceFilter(implicit F: TraverseFilter[F], G: Applicative[G]): G[F[A]] = F.sequenceFilter(fgoa)
}

final class TraverseFilterOps[F[_], G[_], A](private val fa: F[A]) extends AnyVal {

def traverseCollect[B](f: PartialFunction[A, G[B]])(implicit
F: TraverseFilter[F],
G: Applicative[G]
): G[F[B]] =
F.traverseCollect(fa)(f)
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ object SerializableLaws {
ois.close()
Result(status = Proof)
} catch {
case NonFatal(t) =>
case t if NonFatal(t) =>
Result(status = Exception(t))
} finally {
oos.close()
Expand Down
8 changes: 8 additions & 0 deletions laws/src/main/scala/cats/laws/TraverseFilterLaws.scala
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ trait TraverseFilterLaws[F[_]] extends FunctorFilterLaws[F] {
G: Monad[G]
): IsEq[G[F[B]]] =
fa.traverseEither(a => f(a).map(_.toRight(e)))((_, _) => Applicative[G].unit) <-> fa.traverseFilter(f)

def traverseCollectRef[G[_], A, B](fa: F[A], f: PartialFunction[A, G[B]])(implicit
G: Applicative[G]
): IsEq[G[F[B]]] = {
val lhs = fa.traverseCollect(f)
val rhs = fa.traverseFilter(a => f.lift(a).sequence)
lhs <-> rhs
}
}

object TraverseFilterLaws {
Expand Down
16 changes: 14 additions & 2 deletions laws/src/main/scala/cats/laws/discipline/TraverseFilterTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,17 @@ trait TraverseFilterTests[F[_]] extends FunctorFilterTests[F] {
EqFC: Eq[F[C]],
EqGFA: Eq[Option[F[A]]],
EqMNFC: Eq[Nested[Option, Option, F[C]]]
): RuleSet =
): RuleSet = {
implicit val arbFAOB: Arbitrary[PartialFunction[A, Option[B]]] =
Arbitrary(ArbFABoo.arbitrary.map { pfab =>
{
case a if pfab.isDefinedAt(a) =>
val b = pfab(a)
if (((a.hashCode ^ b.hashCode) & 1) == 1) Some(b)
else None
}
})

new DefaultRuleSet(
name = "traverseFilter",
parent = Some(functorFilter[A, B, C]),
Expand All @@ -58,8 +68,10 @@ trait TraverseFilterTests[F[_]] extends FunctorFilterTests[F] {
"filterA consistent with traverseFilter" -> forAll(laws.filterAConsistentWithTraverseFilter[Option, A] _),
"traverseEither consistent with traverseFilter" -> forAll(
laws.traverseEitherConsistentWithTraverseFilter[Option, F[A], A, B] _
)
),
"traverseCollect reference" -> forAll(laws.traverseCollectRef[Option, A, B] _)
)
}
}

object TraverseFilterTests {
Expand Down
8 changes: 6 additions & 2 deletions laws/src/main/scala/cats/laws/discipline/TraverseTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ package discipline

import cats.instances.option._
import cats.kernel.CommutativeMonoid
import org.scalacheck.Prop._
import org.scalacheck.{Arbitrary, Cogen, Prop}
import Prop._

trait TraverseTests[F[_]] extends FunctorTests[F] with FoldableTests[F] with UnorderedTraverseTests[F] {
def laws: TraverseLaws[F]
Expand Down Expand Up @@ -78,7 +78,11 @@ trait TraverseTests[F[_]] extends FunctorTests[F] with FoldableTests[F] with Uno
"traverse ref mapAccumulate" -> forAll(laws.mapAccumulateRef[M, A, C] _),
"traverse ref mapWithIndex" -> forAll(laws.mapWithIndexRef[A, C] _),
"traverse ref traverseWithIndexM" -> forAll(laws.traverseWithIndexMRef[Option, A, C] _),
"traverse ref zipWithIndex" -> forAll(laws.zipWithIndexRef[A, C] _)
"traverse ref zipWithIndex" -> forAll(laws.zipWithIndexRef[A, C] _),
"traverse ref zipWithLongIndex" -> forAll(laws.zipWithLongIndexRef[A, C] _),
"traverse ref mapWithLongIndex" -> forAll(laws.mapWithLongIndexRef[A, C] _),
"traverse ref traverseWithLongIndexM" -> forAll(laws.traverseWithLongIndexMRef[Option, A, C] _),
"traverse ref updated" -> forAll(laws.updatedRef[A, A](_, _, _))
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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)))
}
}
Expand Down
59 changes: 59 additions & 0 deletions tests/shared/src/test/scala/cats/tests/NonEmptySeqSuite.scala
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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))))
Expand Down
13 changes: 1 addition & 12 deletions tests/shared/src/test/scala/cats/tests/SeqSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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]
Expand Down
7 changes: 7 additions & 0 deletions tests/shared/src/test/scala/cats/tests/SyntaxSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -580,4 +580,11 @@ object SyntaxSuite {

val result: Either[A, List[B]] = f.sequenceFilter
}

def testTraverseCollect[A, B]: Unit = {
val list = mock[List[A]]
val f = mock[PartialFunction[A, Option[B]]]

val result: Option[List[B]] = list.traverseCollect(f)
}
}

0 comments on commit ab74f33

Please sign in to comment.