From 84e9641360ade6763c8c19fa86c9510210b89080 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Sat, 18 Aug 2018 18:00:31 +0200 Subject: [PATCH 01/14] Better Chain Arbitrary (#2420) * Better Chain Arbitrary * Fix bug in uncons --- core/src/main/scala/cats/data/Chain.scala | 23 ++++--------------- .../cats/laws/discipline/Arbitrary.scala | 17 +++++++++++--- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/core/src/main/scala/cats/data/Chain.scala b/core/src/main/scala/cats/data/Chain.scala index 974bfcce019..364e9e97653 100644 --- a/core/src/main/scala/cats/data/Chain.scala +++ b/core/src/main/scala/cats/data/Chain.scala @@ -31,8 +31,10 @@ sealed abstract class Chain[+A] { result = Some(a -> next) case Append(l, r) => c = l; rights += r case Wrap(seq) => - val tail = seq.tail - val next = fromSeq(tail) + val tail = fromSeq(seq.tail) + val next = + if (rights.isEmpty) tail + else tail ++ rights.reduceLeft((x, y) => Append(y, x)) result = Some((seq.head, next)) case Empty => if (rights.isEmpty) { @@ -424,22 +426,7 @@ object Chain extends ChainInstances { /** Creates a Chain from the specified elements. */ def apply[A](as: A*): Chain[A] = - as match { - case w: collection.mutable.WrappedArray[A] => - if (w.isEmpty) nil - else if (w.size == 1) one(w.head) - else { - val arr: Array[A] = w.array - var c: Chain[A] = one(arr.last) - var idx = arr.size - 2 - while (idx >= 0) { - c = Append(one(arr(idx)), c) - idx -= 1 - } - c - } - case _ => fromSeq(as) - } + fromSeq(as) // scalastyle:off null class ChainIterator[A](self: Chain[A]) extends Iterator[A] { diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index e42a5c21f2a..e6a644ccc42 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -293,9 +293,20 @@ object arbitrary extends ArbitraryInstances0 { case 1 => A.arbitrary.map(Chain.one) case 2 => A.arbitrary.flatMap(a1 => A.arbitrary.flatMap(a2 => Chain.concat(Chain.one(a1), Chain.one(a2)))) - case n => Chain.fromSeq(Range.apply(0, n)).foldLeft(Gen.const(Chain.empty[A])) { (gen, _) => - gen.flatMap(cat => A.arbitrary.map(a => cat :+ a)) - } + case n => + def fromList(m: Int) = Range.apply(0, m).toList.foldLeft(Gen.const(List.empty[A])) { (gen, _) => + gen.flatMap(list => A.arbitrary.map(_ :: list)) + }.map(Chain.fromSeq) + val split = fromList(n / 2).flatMap(as => fromList(n / 2).map(_ ++ as)) + val appended = fromList(n - 1).flatMap(as => A.arbitrary.map(as :+ _)) + val prepended = fromList(n - 1).flatMap(as => A.arbitrary.map(_ +: as)) + val startEnd = fromList(n - 2).flatMap(as => A.arbitrary.flatMap(a => A.arbitrary.map(_ +: (as :+ a)))) + val betweenListsAndEnd = fromList((n - 1) / 2).flatMap(as => A.arbitrary.flatMap(a => + fromList((n - 1) / 2).flatMap(as2 => A.arbitrary.map(a2 => (as2 ++ (a +: as)) :+ a2)))) + val betweenListsAndFront = fromList((n - 1) / 2).flatMap(as => A.arbitrary.flatMap(a => + fromList((n - 1) / 2).flatMap(as2 => A.arbitrary.map(a2 => a2 +: (as2 ++ (a +: as)))))) + Gen.oneOf(fromList(n), split, appended, prepended, startEnd, betweenListsAndEnd, betweenListsAndFront) + }) implicit def catsLawsCogenForChain[A](implicit A: Cogen[A]): Cogen[Chain[A]] = From a85500e78319c1f673398f117426e591580aea9f Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sat, 18 Aug 2018 12:49:15 -0700 Subject: [PATCH 02/14] Add scaladoc example for Semigroupal.product --- core/src/main/scala/cats/Semigroupal.scala | 26 ++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/core/src/main/scala/cats/Semigroupal.scala b/core/src/main/scala/cats/Semigroupal.scala index 6fed1ca0e94..500e9cd010a 100644 --- a/core/src/main/scala/cats/Semigroupal.scala +++ b/core/src/main/scala/cats/Semigroupal.scala @@ -13,6 +13,32 @@ import simulacrum.typeclass * [[Semigroupal]] and [[Functor]] to illustrate this. */ @typeclass trait Semigroupal[F[_]] { + + /** + * Combine an `F[A]` and an `F[B]` into an `F[(A, B)]` that maintains the effects of both `fa` and `fb`. + * + * Example: + * {{{ + * scala> import cats.implicits._ + * + * scala> val noneInt: Option[Int] = None + * scala> val some3: Option[Int] = Some(3) + * scala> val noneString: Option[String] = None + * scala> val someFoo: Option[String] = Some("foo") + * + * scala> Semigroupal[Option].product(noneInt, noneString) + * res0: Option[(Int, String)] = None + * + * scala> Semigroupal[Option].product(noneInt, someFoo) + * res1: Option[(Int, String)] = None + * + * scala> Semigroupal[Option].product(some3, noneString) + * res2: Option[(Int, String)] = None + * + * scala> Semigroupal[Option].product(some3, someFoo) + * res3: Option[(Int, String)] = Some((3,foo)) + * }}} + */ def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] } From c3b3ad674476aaf9e12c2533ab67752d08e973a8 Mon Sep 17 00:00:00 2001 From: Francisco Bermejo Date: Mon, 20 Aug 2018 16:00:54 +0200 Subject: [PATCH 03/14] Add Tecsisa to adopters (#2428) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 56f53d61081..a772adb9aab 100644 --- a/README.md +++ b/README.md @@ -218,6 +218,7 @@ Here's a (non-exhaustive) list of companies that use Cats in production. Don't s - [Spotify](https://www.spotify.com) - [SpringerNature](https://www.springernature.com) - [Stripe](https://stripe.com) +- [Tecsisa](https://www.tecsisa.com) - [Teikametrics](http://teikametrics.com) - [The Guardian](https//www.theguardian.com) - [Underscore Consulting](https://underscore.io/) From fe409cf3e9fee53e380d5fd5d4d27073286e963b Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Mon, 20 Aug 2018 07:57:08 -0700 Subject: [PATCH 04/14] Override size in Chain instance (#2425) * Override size in Chain instance This should be a slight optimization. * Update Chain.scala --- core/src/main/scala/cats/data/Chain.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/main/scala/cats/data/Chain.scala b/core/src/main/scala/cats/data/Chain.scala index 364e9e97653..5b66f5a878b 100644 --- a/core/src/main/scala/cats/data/Chain.scala +++ b/core/src/main/scala/cats/data/Chain.scala @@ -538,6 +538,7 @@ private[data] sealed abstract class ChainInstances extends ChainInstances1 { override def exists[A](fa: Chain[A])(p: A => Boolean): Boolean = fa.exists(p) override def forall[A](fa: Chain[A])(p: A => Boolean): Boolean = fa.forall(p) override def find[A](fa: Chain[A])(f: A => Boolean): Option[A] = fa.find(f) + override def size[A](fa: Chain[A]): Long = fa.length def coflatMap[A, B](fa: Chain[A])(f: Chain[A] => B): Chain[B] = { @tailrec def go(as: Chain[A], res: ListBuffer[B]): Chain[B] = From b565a895c9c0c8ba4993bdd0dbdde877eedb2ae1 Mon Sep 17 00:00:00 2001 From: Leandro Date: Tue, 21 Aug 2018 08:31:23 -0300 Subject: [PATCH 05/14] Update AUTHORS.md (#2422) * Update AUTHORS.md * Update AUTHORS.md --- AUTHORS.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/AUTHORS.md b/AUTHORS.md index 566870a6642..e8bb8e10a8c 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -28,6 +28,7 @@ possible: * Allan Timothy Leong * Alonso Dominguez * Amir Mohammad Saied + * Amitay Horwitz * Andrea Fiore * Andrea McAts * Andrew Jones @@ -46,6 +47,7 @@ possible: * Bobby Rauchenberg * Brendan McAdams * Brian McKenna + * Brian P. Holt * Bryan Tan * Cary Robbins * Chris Birchall @@ -55,7 +57,9 @@ possible: * Colt Frederickson * Connie Chen * Csongor Kiss + * dadepo * Dale Wijnand + * Daniel Karch * Daniel Spiewak * Daniel Urban * Daniela Sfregola @@ -64,14 +68,17 @@ possible: * David Allsopp * David Gregory * David R. Bild + * Dayyan Lord * Denis Mikhaylov * Denis Rosca * Denis * Derek Wickern * Diego Esteban Alonso Blas + * Donaldo Salas * Earl St Sauver * Edd Steel * Eric Torreborre + * ericaovo * Erik LaBianca * Erik Osheim * Eugene Burmako @@ -80,12 +87,16 @@ possible: * Fabian Gutierrez * Fabian Schmitthenner * Fabio Labella + * fantayeneh * Feynman Liang * Filipe Oliveira * Filippo Mariotti + * Francisco Bermejo * Francisco Canedo + * Francois Armand * Frank S. Thomas * Gabriele Petronella + * Gavin Bisesi * Giovanni Ruggiero * Giulio De Luise * Greg Pfeil @@ -103,6 +114,8 @@ possible: * Jan-Hendrik Zab * Jean-Rémi Desjardins * Jens + * Jens Grassel + * Jimin Hsieh * Jisoo Park * João Ferreira * John Sullivan @@ -112,10 +125,12 @@ possible: * Josh Marcus * Juan Pedro Moreno * Juan Valencia + * Jules Ivanic * Julien Richard-Foy * Julien Truffaut * Jun Tomioka * Kailuo Wang + * Keir Lawson * kellen * Kenji Yoshida * Lars Hupel @@ -130,8 +145,10 @@ possible: * Marc Siegel * Marcin Rzeźnicki * Marco Battaglia + * Mariot Chauvin * Mark de Jong * Markus Hauck + * Martijn Hoekstra * mathhun * Matt Martin * Matthias Lüneberg @@ -142,6 +159,7 @@ possible: * Mike Curry * Miles Sabin * nigredo-tori + * Nikolay Maksimenko * n4to4 * Olivier Blanvillain * Olli Helenius @@ -154,12 +172,14 @@ possible: * Paulo "JCranky" Siqueira * Pavel Chlupacek * Pavkin Vladimir + * Paweł Lipski * Pepe García * Pere Villega * Peter Neyens * Peter Perhac * phderome * Philip Wills + * Piotr Gawryś * Rafa Paradela * Raúl Raja Martínez * RawToast @@ -171,8 +191,10 @@ possible: * Rohan Shah * Romain Ruetschi * Ross A. Baker + * rsekulski * rsoeldner * Rüdiger Klaehn + * Rutvik Patel * Ryan Case * Sam Ritchie * Sarunas Valaskevicius @@ -181,6 +203,7 @@ possible: * Shunsuke Otani * Simeon H. K. Fitch * Sinisa Louc + * Song Kun * Stephen Carman * Stephen Judkins * Stephen Lazaro @@ -193,6 +216,7 @@ possible: * Tom Switzer * Tomas Mikula * Tongfei Chen + * Torsten Schmits * Travis Brown * Trond Bjerkestrand * Tya @@ -210,7 +234,10 @@ possible: * Yilin Wei * Zach Abbott * zainab-ali + * Zelenya + * zhen * Ziyang Liu + * λoλcat Cats has been heavily inspired by many libraries, including [Scalaz](https://github.com/scalaz/scalaz), Haskell's [Prelude](https://hackage.haskell.org/package/base-4.9.0.0/docs/Prelude.html), and others. From 50ac2fccc7fdc299e2580c8f875eb6a8f7ca663d Mon Sep 17 00:00:00 2001 From: Geir Hedemark Date: Tue, 21 Aug 2018 13:31:52 +0200 Subject: [PATCH 06/14] Add Basefarm as adopter to README.md (#2433) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a772adb9aab..70af27e75f0 100644 --- a/README.md +++ b/README.md @@ -190,6 +190,7 @@ Here's a (non-exhaustive) list of companies that use Cats in production. Don't s - [AutoScout24](https://www.autoscout24.com) - [Avast](https://avast.com) - [Banno Group inside of Jack Henry & Associates](https://banno.com/) +- [Basefarm](https://basefarm.com/) - [buildo](https://buildo.io) - [Codacy](https://www.codacy.com/) - [Codecentric](https://codecentric.de) From 772bb1d1068fbba79f481d936195478725137523 Mon Sep 17 00:00:00 2001 From: "P. Oscar Boykin" Date: Tue, 21 Aug 2018 16:46:34 -0700 Subject: [PATCH 07/14] Make Chain Arbitraries recursively build concatenations (#2430) * Make Chain Arbitraries recursively build concatenations * check some emptyness invariants * improve code coverage by leveraging invariants * test next throws the right exception * remove runtime check * make Chain iterators private --- core/src/main/scala/cats/data/Chain.scala | 40 ++++++------- .../cats/laws/discipline/Arbitrary.scala | 57 ++++++++++--------- .../test/scala/cats/tests/ChainSuite.scala | 41 +++++++++++++ 3 files changed, 88 insertions(+), 50 deletions(-) diff --git a/core/src/main/scala/cats/data/Chain.scala b/core/src/main/scala/cats/data/Chain.scala index 5b66f5a878b..916c79f131e 100644 --- a/core/src/main/scala/cats/data/Chain.scala +++ b/core/src/main/scala/cats/data/Chain.scala @@ -37,12 +37,8 @@ sealed abstract class Chain[+A] { else tail ++ rights.reduceLeft((x, y) => Append(y, x)) result = Some((seq.head, next)) case Empty => - if (rights.isEmpty) { - result = None - } else { - c = rights.last - rights.trimEnd(1) - } + // Empty is only top level, it is never internal to an Append + result = None } } // scalastyle:on null @@ -297,12 +293,8 @@ sealed abstract class Chain[+A] { else rights.reduceLeft((x, y) => Append(y, x)) rights.clear() case Empty => - if (rights.isEmpty) { - c = null - } else { - c = rights.last - rights.trimEnd(1) - } + // Empty is only top level, it is never internal to an Append + c = null } } } @@ -429,10 +421,10 @@ object Chain extends ChainInstances { fromSeq(as) // scalastyle:off null - class ChainIterator[A](self: Chain[A]) extends Iterator[A] { - var c: Chain[A] = if (self.isEmpty) null else self - val rights = new collection.mutable.ArrayBuffer[Chain[A]] - var currentIterator: Iterator[A] = null + private class ChainIterator[A](self: Chain[A]) extends Iterator[A] { + private[this] var c: Chain[A] = if (self.isEmpty) null else self + private[this] val rights = new collection.mutable.ArrayBuffer[Chain[A]] + private[this] var currentIterator: Iterator[A] = null override def hasNext: Boolean = (c ne null) || ((currentIterator ne null) && currentIterator.hasNext) @@ -461,8 +453,8 @@ object Chain extends ChainInstances { rights.clear() currentIterator = seq.iterator currentIterator.next - case Empty => - go // This shouldn't happen + case null | Empty => + throw new java.util.NoSuchElementException("next called on empty iterator") } } @@ -473,10 +465,10 @@ object Chain extends ChainInstances { // scalastyle:off null - class ChainReverseIterator[A](self: Chain[A]) extends Iterator[A] { - var c: Chain[A] = if (self.isEmpty) null else self - val lefts = new collection.mutable.ArrayBuffer[Chain[A]] - var currentIterator: Iterator[A] = null + private class ChainReverseIterator[A](self: Chain[A]) extends Iterator[A] { + private[this] var c: Chain[A] = if (self.isEmpty) null else self + private[this] val lefts = new collection.mutable.ArrayBuffer[Chain[A]] + private[this] var currentIterator: Iterator[A] = null override def hasNext: Boolean = (c ne null) || ((currentIterator ne null) && currentIterator.hasNext) @@ -505,8 +497,8 @@ object Chain extends ChainInstances { lefts.clear() currentIterator = seq.reverseIterator currentIterator.next - case Empty => - go // This shouldn't happen + case null | Empty => + throw new java.util.NoSuchElementException("next called on empty iterator") } } diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index e6a644ccc42..80675a42970 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -77,12 +77,12 @@ object arbitrary extends ArbitraryInstances0 { Cogen[List[A]].contramap(_.toList) implicit def catsLawsArbitraryForNonEmptyChain[A](implicit A: Arbitrary[A]): Arbitrary[NonEmptyChain[A]] = - Arbitrary(implicitly[Arbitrary[Chain[A]]].arbitrary.flatMap(fa => - Gen.oneOf( - A.arbitrary.map(a => NonEmptyChain.fromChainPrepend(a, fa)), - A.arbitrary.map(a => NonEmptyChain.fromChainAppend(fa, a)), - A.arbitrary.map(NonEmptyChain.one) - ))) + Arbitrary(implicitly[Arbitrary[Chain[A]]].arbitrary.flatMap { chain => + NonEmptyChain.fromChain(chain) match { + case None => A.arbitrary.map(NonEmptyChain.one) + case Some(ne) => Gen.const(ne) + } + }) implicit def catsLawsCogenForNonEmptyChain[A](implicit A: Cogen[A]): Cogen[NonEmptyChain[A]] = Cogen[Chain[A]].contramap(_.toChain) @@ -287,27 +287,32 @@ object arbitrary extends ArbitraryInstances0 { implicit def catsLawsCogenForAndThen[A, B](implicit F: Cogen[A => B]): Cogen[AndThen[A, B]] = Cogen((seed, x) => F.perturb(seed, x)) - implicit def catsLawsArbitraryForChain[A](implicit A: Arbitrary[A]): Arbitrary[Chain[A]] = - Arbitrary(Gen.sized { - case 0 => Gen.const(Chain.nil) - case 1 => A.arbitrary.map(Chain.one) - case 2 => A.arbitrary.flatMap(a1 => A.arbitrary.flatMap(a2 => - Chain.concat(Chain.one(a1), Chain.one(a2)))) - case n => - def fromList(m: Int) = Range.apply(0, m).toList.foldLeft(Gen.const(List.empty[A])) { (gen, _) => - gen.flatMap(list => A.arbitrary.map(_ :: list)) - }.map(Chain.fromSeq) - val split = fromList(n / 2).flatMap(as => fromList(n / 2).map(_ ++ as)) - val appended = fromList(n - 1).flatMap(as => A.arbitrary.map(as :+ _)) - val prepended = fromList(n - 1).flatMap(as => A.arbitrary.map(_ +: as)) - val startEnd = fromList(n - 2).flatMap(as => A.arbitrary.flatMap(a => A.arbitrary.map(_ +: (as :+ a)))) - val betweenListsAndEnd = fromList((n - 1) / 2).flatMap(as => A.arbitrary.flatMap(a => - fromList((n - 1) / 2).flatMap(as2 => A.arbitrary.map(a2 => (as2 ++ (a +: as)) :+ a2)))) - val betweenListsAndFront = fromList((n - 1) / 2).flatMap(as => A.arbitrary.flatMap(a => - fromList((n - 1) / 2).flatMap(as2 => A.arbitrary.map(a2 => a2 +: (as2 ++ (a +: as)))))) - Gen.oneOf(fromList(n), split, appended, prepended, startEnd, betweenListsAndEnd, betweenListsAndFront) + implicit def catsLawsArbitraryForChain[A](implicit A: Arbitrary[A]): Arbitrary[Chain[A]] = { + val genA = A.arbitrary + + def genSize(sz: Int): Gen[Chain[A]] = { + val fromSeq = Gen.listOfN(sz, genA).map(Chain.fromSeq) + val recursive = + sz match { + case 0 => Gen.const(Chain.nil) + case 1 => genA.map(Chain.one) + case n => + // Here we concat two chains + for { + n0 <- Gen.choose(1, n-1) + n1 = n - n0 + left <- genSize(n0) + right <- genSize(n1) + } yield left ++ right + } + + // prefer to generate recursively built Chains + // but sometimes create fromSeq + Gen.frequency((5, recursive), (1, fromSeq)) + } - }) + Arbitrary(Gen.sized(genSize)) + } implicit def catsLawsCogenForChain[A](implicit A: Cogen[A]): Cogen[Chain[A]] = Cogen[List[A]].contramap(_.toList) diff --git a/tests/src/test/scala/cats/tests/ChainSuite.scala b/tests/src/test/scala/cats/tests/ChainSuite.scala index 3fc857c8080..0a4c23e4cd5 100644 --- a/tests/src/test/scala/cats/tests/ChainSuite.scala +++ b/tests/src/test/scala/cats/tests/ChainSuite.scala @@ -126,4 +126,45 @@ class ChainSuite extends CatsSuite { ci.reverse.toList should === (ci.toList.reverse) } } + + test("(a ++ b).isEmpty ==> a.isEmpty and b.isEmpty") { + forAll { (a: Chain[Int], b: Chain[Int]) => + assert((a ++ b).nonEmpty || (a.isEmpty && b.isEmpty)) + } + } + + test("a.isEmpty == (a eq Chain.nil)") { + assert(Chain.fromSeq(Nil) eq Chain.nil) + + forAll { (a: Chain[Int]) => + assert(a.isEmpty == (a eq Chain.nil)) + } + } + + test("(nil ++ a) eq a") { + forAll { (a: Chain[Int]) => + assert((Chain.nil ++ a) eq a) + assert((a ++ Chain.nil) eq a) + } + } + + test("Chain.iterator.next should throw NoSuchElementException") { + forAll { (a: Chain[Int]) => + val it = a.iterator + + while(it.hasNext) it.next + + intercept[java.util.NoSuchElementException] { + it.next + } + + val rit = a.reverseIterator + + while(rit.hasNext) rit.next + + intercept[java.util.NoSuchElementException] { + rit.next + } + } + } } From ab157bb8f98dd7d6e146c9cb4c319a32a1e716f0 Mon Sep 17 00:00:00 2001 From: Akshay Sachdeva Date: Wed, 22 Aug 2018 15:30:43 -0500 Subject: [PATCH 08/14] Update README.md (#2435) Adding Spiceworks as one of the adopters --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 70af27e75f0..78c3dc545c9 100644 --- a/README.md +++ b/README.md @@ -215,6 +215,7 @@ Here's a (non-exhaustive) list of companies that use Cats in production. Don't s - [Scalac](https://scalac.io) - [Scala Center](https://scala.epfl.ch) - [Snowplow Analytics](https://snowplowanalytics.com/) +- [Spiceworks](https://www.spiceworks.com/) - [Spotahome](https://spotahome.com) - [Spotify](https://www.spotify.com) - [SpringerNature](https://www.springernature.com) From d37dcaccb9863e594922065cf93082806e64aa01 Mon Sep 17 00:00:00 2001 From: Stew O'Connor Date: Fri, 24 Aug 2018 09:18:39 -0700 Subject: [PATCH 09/14] remove stew --- AUTHORS.md | 1 - README.md | 2 -- build.sbt | 5 ----- 3 files changed, 8 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index e8bb8e10a8c..be2ca8c30bf 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -207,7 +207,6 @@ possible: * Stephen Carman * Stephen Judkins * Stephen Lazaro - * Stew O'Connor * Suhas Gaddam * sullis * Sumedh Mungee diff --git a/README.md b/README.md index 78c3dc545c9..c7b994d6a9a 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,6 @@ By sharing the same set of type classes, instances and data types provided by Ca #### General purpose libraries to support pure functional programming - * [Dogs](https://github.com/stew/dogs): pure functional collections and data structures * [droste](https://github.com/andyscott/droste): recursion schemes for Cats * [Dsl.scala](https://github.com/ThoughtWorksInc/Dsl.scala): The `!`-notation for creating Cats monadic expressions * [eff](https://github.com/atnos-org/eff): functional effects and effect handlers (alternative to monad transformers) @@ -241,7 +240,6 @@ The current maintainers (people who can merge pull requests) are: * [LukaJCB](https://github.com/LukaJCB) Luka Jacobowitz * [peterneyens](https://github.com/peterneyens) Peter Neyens * [tpolecat](https://github.com/tpolecat) Rob Norris - * [stew](https://github.com/stew) Mike O'Connor * [non](https://github.com/non) Erik Osheim * [mpilquist](https://github.com/mpilquist) Michael Pilquist * [milessabin](https://github.com/milessabin) Miles Sabin diff --git a/build.sbt b/build.sbt index fee016a7fac..b161b3a5d54 100644 --- a/build.sbt +++ b/build.sbt @@ -556,11 +556,6 @@ lazy val publishSettings = Seq( Rob Norris https://github.com/tpolecat/ - - stew - Mike O'Connor - https://github.com/stew/ - non Erik Osheim From d10bddf672408dc2d6aa4595c9249cc641ba7e48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=BBo=CE=BBcat?= <40268503+catostrophe@users.noreply.github.com> Date: Tue, 28 Aug 2018 19:08:54 +0300 Subject: [PATCH 10/14] Add/fix Foldable extensions: findM and collectFirstSomeM (#2421) * Add findM to Foldable extensions * Empty commit (trigger Travis CI build) * Reimplement monadic fold extensions stack-safely Fix implementation for findM and collectFirstSomeM extensions for Foldable vai tailRecM, make Foldable.Source package private * Add test for .collectFirstSomeM short-circuiting --- core/src/main/scala/cats/Foldable.scala | 4 +- .../src/main/scala/cats/syntax/foldable.scala | 58 ++++++++++++--- .../test/scala/cats/tests/FoldableSuite.scala | 74 ++++++++++++++----- 3 files changed, 104 insertions(+), 32 deletions(-) diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index 2e88a25676d..cd0047debf8 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -601,11 +601,11 @@ object Foldable { * It could be made a value class after * https://github.com/scala/bug/issues/9600 is resolved. */ - private sealed abstract class Source[+A] { + private[cats] sealed abstract class Source[+A] { def uncons: Option[(A, Eval[Source[A]])] } - private object Source { + private[cats] object Source { val Empty: Source[Nothing] = new Source[Nothing] { def uncons = None } diff --git a/core/src/main/scala/cats/syntax/foldable.scala b/core/src/main/scala/cats/syntax/foldable.scala index 86c50c62ce0..3d5b60c130a 100644 --- a/core/src/main/scala/cats/syntax/foldable.scala +++ b/core/src/main/scala/cats/syntax/foldable.scala @@ -95,29 +95,67 @@ final class FoldableOps[F[_], A](val fa: F[A]) extends AnyVal { /** * Monadic version of `collectFirstSome`. + * + * If there are no elements, the result is `None`. `collectFirstSomeM` short-circuits, + * i.e. once a Some element is found, no further effects are produced. + * + * For example: * {{{ * scala> import cats.implicits._ * scala> def parseInt(s: String): Either[String, Int] = Either.catchOnly[NumberFormatException](s.toInt).leftMap(_.getMessage) * scala> val keys1 = List("1", "2", "4", "5") * scala> val map1 = Map(4 -> "Four", 5 -> "Five") * scala> keys1.collectFirstSomeM(parseInt(_) map map1.get) - * res1: scala.util.Either[String,Option[String]] = Right(Some(Four)) + * res0: scala.util.Either[String,Option[String]] = Right(Some(Four)) + * * scala> val map2 = Map(6 -> "Six", 7 -> "Seven") * scala> keys1.collectFirstSomeM(parseInt(_) map map2.get) - * res2: scala.util.Either[String,Option[String]] = Right(None) + * res1: scala.util.Either[String,Option[String]] = Right(None) + * * scala> val keys2 = List("1", "x", "4", "5") * scala> keys2.collectFirstSomeM(parseInt(_) map map1.get) - * res3: scala.util.Either[String,Option[String]] = Left(For input string: "x") + * res2: scala.util.Either[String,Option[String]] = Left(For input string: "x") + * * scala> val keys3 = List("1", "2", "4", "x") * scala> keys3.collectFirstSomeM(parseInt(_) map map1.get) - * res4: scala.util.Either[String,Option[String]] = Right(Some(Four)) + * res3: scala.util.Either[String,Option[String]] = Right(Some(Four)) * }}} */ def collectFirstSomeM[G[_], B](f: A => G[Option[B]])(implicit F: Foldable[F], G: Monad[G]): G[Option[B]] = - F.foldRight(fa, Eval.now(G.pure(Option.empty[B])))((a, lb) => - Eval.now(G.flatMap(f(a)) { - case None => lb.value - case s => G.pure(s) - }) - ).value + G.tailRecM(Foldable.Source.fromFoldable(fa))(_.uncons match { + case Some((a, src)) => G.map(f(a)) { + case None => Left(src.value) + case s => Right(s) + } + case None => G.pure(Right(None)) + }) + + /** + * Find the first element matching the effectful predicate, if one exists. + * + * If there are no elements, the result is `None`. `findM` short-circuits, + * i.e. once an element is found, no further effects are produced. + * + * For example: + * {{{ + * scala> import cats.implicits._ + * scala> val list = List(1,2,3,4) + * scala> list.findM(n => (n >= 2).asRight[String]) + * res0: Either[String,Option[Int]] = Right(Some(2)) + * + * scala> list.findM(n => (n > 4).asRight[String]) + * res1: Either[String,Option[Int]] = Right(None) + * + * scala> list.findM(n => Either.cond(n < 3, n >= 2, "error")) + * res2: Either[String,Option[Int]] = Right(Some(2)) + * + * scala> list.findM(n => Either.cond(n < 3, false, "error")) + * res3: Either[String,Option[Int]] = Left(error) + * }}} + */ + def findM[G[_]](p: A => G[Boolean])(implicit F: Foldable[F], G: Monad[G]): G[Option[A]] = + G.tailRecM(Foldable.Source.fromFoldable(fa))(_.uncons match { + case Some((a, src)) => G.map(p(a))(if (_) Right(Some(a)) else Left(src.value)) + case None => G.pure(Right(None)) + }) } diff --git a/tests/src/test/scala/cats/tests/FoldableSuite.scala b/tests/src/test/scala/cats/tests/FoldableSuite.scala index 800929f2dac..f37fcd816b3 100644 --- a/tests/src/test/scala/cats/tests/FoldableSuite.scala +++ b/tests/src/test/scala/cats/tests/FoldableSuite.scala @@ -85,11 +85,12 @@ abstract class FoldableSuite[F[_]: Foldable](name: String)( } } - test(s"Foldable[$name].find/exists/forall/existsM/forallM/filter_/dropWhile_") { + test(s"Foldable[$name].find/exists/forall/findM/existsM/forallM/filter_/dropWhile_") { forAll { (fa: F[Int], n: Int) => fa.find(_ > n) should === (iterator(fa).find(_ > n)) fa.exists(_ > n) should === (iterator(fa).exists(_ > n)) fa.forall(_ > n) should === (iterator(fa).forall(_ > n)) + fa.findM(k => Option(k > n)) should === (Option(iterator(fa).find(_ > n))) fa.existsM(k => Option(k > n)) should === (Option(iterator(fa).exists(_ > n))) fa.forallM(k => Option(k > n)) should === (Option(iterator(fa).forall(_ > n))) fa.filter_(_ > n) should === (iterator(fa).filter(_ > n).toList) @@ -199,16 +200,42 @@ class FoldableSuiteAdditional extends CatsSuite { larger.value should === (large.map(_ + 1)) } - def checkFoldMStackSafety[F[_]](fromRange: Range => F[Int])(implicit F: Foldable[F]): Unit = { + def checkMonadicFoldsStackSafety[F[_]](fromRange: Range => F[Int])(implicit F: Foldable[F]): Unit = { def nonzero(acc: Long, x: Int): Option[Long] = if (x == 0) None else Some(acc + x) + def gte(lb: Int, x: Int): Option[Boolean] = + if (x >= lb) Some(true) else Some(false) + + def gteSome(lb: Int, x: Int): Option[Option[Int]] = + if (x >= lb) Some(Some(x)) else Some(None) + val n = 100000 - val expected = n.toLong*(n.toLong+1)/2 - val foldMResult = F.foldM(fromRange(1 to n), 0L)(nonzero) - assert(foldMResult.get == expected) + val src = fromRange(1 to n) + + val foldMExpected = n.toLong*(n.toLong+1)/2 + val foldMResult = F.foldM(src, 0L)(nonzero) + assert(foldMResult.get == foldMExpected) + + val existsMExpected = true + val existsMResult = F.existsM(src)(gte(n, _)) + assert(existsMResult.get == existsMExpected) + + val forallMExpected = true + val forallMResult = F.forallM(src)(gte(0, _)) + assert(forallMResult.get == forallMExpected) + + val findMExpected = Some(n) + val findMResult = src.findM(gte(n, _)) + assert(findMResult.get == findMExpected) + + val collectFirstSomeMExpected = Some(n) + val collectFirstSomeMResult = src.collectFirstSomeM(gteSome(n, _)) + assert(collectFirstSomeMResult.get == collectFirstSomeMExpected) + () } + test(s"Foldable.iterateRight") { forAll { (fa: List[Int]) => val eval = Foldable.iterateRight(fa, Eval.later(0)) { (a, eb) => @@ -222,36 +249,36 @@ class FoldableSuiteAdditional extends CatsSuite { } } - test("Foldable[List].foldM stack safety") { - checkFoldMStackSafety[List](_.toList) + test("Foldable[List].foldM/existsM/forallM/findM/collectFirstSomeM stack safety") { + checkMonadicFoldsStackSafety[List](_.toList) } test("Foldable[Stream].foldM stack safety") { - checkFoldMStackSafety[Stream](_.toStream) + checkMonadicFoldsStackSafety[Stream](_.toStream) } - test("Foldable[Vector].foldM stack safety") { - checkFoldMStackSafety[Vector](_.toVector) + test("Foldable[Vector].foldM/existsM/forallM/findM/collectFirstSomeM stack safety") { + checkMonadicFoldsStackSafety[Vector](_.toVector) } - test("Foldable[SortedSet].foldM stack safety") { - checkFoldMStackSafety[SortedSet](s => SortedSet(s:_*)) + test("Foldable[SortedSet].foldM/existsM/forallM/findM/collectFirstSomeM stack safety") { + checkMonadicFoldsStackSafety[SortedSet](s => SortedSet(s:_*)) } - test("Foldable[SortedMap[String, ?]].foldM stack safety") { - checkFoldMStackSafety[SortedMap[String, ?]](xs => SortedMap.empty[String, Int] ++ xs.map(x => x.toString -> x).toMap) + test("Foldable[SortedMap[String, ?]].foldM/existsM/forallM/findM/collectFirstSomeM stack safety") { + checkMonadicFoldsStackSafety[SortedMap[String, ?]](xs => SortedMap.empty[String, Int] ++ xs.map(x => x.toString -> x).toMap) } - test("Foldable[NonEmptyList].foldM stack safety") { - checkFoldMStackSafety[NonEmptyList](xs => NonEmptyList.fromListUnsafe(xs.toList)) + test("Foldable[NonEmptyList].foldM/existsM/forallM/findM/collectFirstSomeM stack safety") { + checkMonadicFoldsStackSafety[NonEmptyList](xs => NonEmptyList.fromListUnsafe(xs.toList)) } - test("Foldable[NonEmptyVector].foldM stack safety") { - checkFoldMStackSafety[NonEmptyVector](xs => NonEmptyVector.fromVectorUnsafe(xs.toVector)) + test("Foldable[NonEmptyVector].foldM/existsM/forallM/findM/collectFirstSomeM stack safety") { + checkMonadicFoldsStackSafety[NonEmptyVector](xs => NonEmptyVector.fromVectorUnsafe(xs.toVector)) } - test("Foldable[NonEmptyStream].foldM stack safety") { - checkFoldMStackSafety[NonEmptyStream](xs => NonEmptyStream(xs.head, xs.tail: _*)) + test("Foldable[NonEmptyStream].foldM/existsM/forallM/findM/collectFirstSomeM stack safety") { + checkMonadicFoldsStackSafety[NonEmptyStream](xs => NonEmptyStream(xs.head, xs.tail: _*)) } test("Foldable[Stream]") { @@ -324,6 +351,13 @@ class FoldableSuiteAdditional extends CatsSuite { assert(F.forallM[Id, Boolean](false #:: boom)(identity) == false) } + test(".findM/.collectFirstSomeM short-circuiting") { + implicit val F = foldableStreamWithDefaultImpl + def boom: Stream[Int] = sys.error("boom") + assert((1 #:: boom).findM[Id](_ > 0) == Some(1)) + assert((1 #:: boom).collectFirstSomeM[Id, Int](Option.apply) == Some(1)) + } + test("Foldable[List] doesn't break substitution") { val result = List.range(0,10).foldM(List.empty[Int])((accum, elt) => Eval.always(elt :: accum)) From fef34e8e2d587ec0f5e0f46a081ef74520e69bce Mon Sep 17 00:00:00 2001 From: Omer van Kloeten Date: Wed, 29 Aug 2018 11:12:42 +0300 Subject: [PATCH 11/14] Added WeWork to list of Adopters (#2445) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c7b994d6a9a..7e9ccabf00e 100644 --- a/README.md +++ b/README.md @@ -224,6 +224,7 @@ Here's a (non-exhaustive) list of companies that use Cats in production. Don't s - [The Guardian](https//www.theguardian.com) - [Underscore Consulting](https://underscore.io/) - [Wegtam GmbH](https://www.wegtam.com) +- [WeWork](https://www.wework.com) - [Wix.com](https://www.wix.com) - [Zalando](https://zalando.com) - [47 Degrees](https://www.47deg.com) From 8da9f79d7bf384f208a6b9919086f450ed5c9e35 Mon Sep 17 00:00:00 2001 From: Christopher Davenport Date: Wed, 29 Aug 2018 09:39:38 -0400 Subject: [PATCH 12/14] Add Various Projects to Ecosystem List (#2444) --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 7e9ccabf00e..e6c4ecdbc84 100644 --- a/README.md +++ b/README.md @@ -76,10 +76,14 @@ By sharing the same set of type classes, instances and data types provided by Ca #### General purpose libraries to support pure functional programming + * [cats-par](https://github.com/ChristopherDavenport/cats-par): Abstract type member Parallel instances + * [cats-scalacheck](https://github.com/ChristopherDavenport/cats-scalacheck): cats typeclass instances for scalacheck + * [cats-time](https://github.com/ChristopherDavenport/cats-time): cats typeclass instances for java time * [droste](https://github.com/andyscott/droste): recursion schemes for Cats * [Dsl.scala](https://github.com/ThoughtWorksInc/Dsl.scala): The `!`-notation for creating Cats monadic expressions * [eff](https://github.com/atnos-org/eff): functional effects and effect handlers (alternative to monad transformers) * [Freestyle](https://github.com/frees-io/freestyle): pure functional framework for Free and Tagless Final apps & libs + * [fuuid](https://github.com/ChristopherDavenport/fuuid): functional uuid's * [iota](https://github.com/frees-io/iota): Fast [co]product types with a clean syntax * [Monocle](https://github.com/julien-truffaut/Monocle): an optics library for Scala (and Scala.js) strongly inspired by Haskell Lens. * [newts](https://github.com/julien-truffaut/newts): Defines newtypes compatible with Cats type classes @@ -92,6 +96,7 @@ By sharing the same set of type classes, instances and data types provided by Ca * [atto](https://github.com/tpolecat/atto): friendly little text parsers * [circe](https://github.com/circe/circe): pure functional JSON library * [Ciris](https://github.com/vlovgr/ciris): Lightweight, extensible, and validated configuration loading in Scala + * [cormorant](https://github.com/ChristopherDavenport/cormorant): CSV handling library for FP * [decline](https://github.com/bkirwi/decline): A composable command-line parser * [doobie](https://github.com/tpolecat/doobie): a pure functional JDBC layer for Scala * [fastparse-cats](https://github.com/johnynek/fastparse-cats): cats Monad and Alternative instances for [fastparse](https://github.com/lihaoyi/fastparse) @@ -105,6 +110,8 @@ By sharing the same set of type classes, instances and data types provided by Ca * [http4s](https://github.com/http4s/http4s): A minimal, idiomatic Scala interface for HTTP * [monadic-html](https://github.com/OlivierBlanvillain/monadic-html): Tiny DOM binding library for Scala.js * [Monix](https://github.com/monix/monix): high-performance library for composing asynchronous and event-based programs + * [linebacker](https://github.com/ChristopherDavenport/linebacker): functional thread pool management + * [log4cats](https://github.com/ChristopherDavenport/log4cats): functional logging * [pureconfig](https://github.com/pureconfig/pureconfig): A boilerplate-free library for loading configuration files * [rainier](https://github.com/stripe/rainier): Bayesian inference in Scala * [scanamo](https://github.com/guardian/scanamo): simpler DynamoDB access for Scala From 380f72153871e6b847a37a9b3150abc0f10f12a7 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Sat, 1 Sep 2018 02:41:27 +0200 Subject: [PATCH 13/14] Give NonEmptyChain more presence (#2431) * Give NonEmptyChain more presence * Small tweak --- core/src/main/scala/cats/data/EitherT.scala | 9 ++++ core/src/main/scala/cats/data/Validated.scala | 37 ++++++++++++++- core/src/main/scala/cats/data/package.scala | 3 ++ core/src/main/scala/cats/syntax/all.scala | 3 ++ core/src/main/scala/cats/syntax/either.scala | 46 ++++++++++++++++++- core/src/main/scala/cats/syntax/list.scala | 29 +++++++++++- core/src/main/scala/cats/syntax/package.scala | 6 +-- .../main/scala/cats/syntax/validated.scala | 33 ++++++++++++- .../test/scala/cats/tests/EitherSuite.scala | 1 + .../test/scala/cats/tests/EitherTSuite.scala | 12 +++++ .../test/scala/cats/tests/SyntaxSuite.scala | 9 +++- .../scala/cats/tests/ValidatedSuite.scala | 6 +++ 12 files changed, 186 insertions(+), 8 deletions(-) diff --git a/core/src/main/scala/cats/data/EitherT.scala b/core/src/main/scala/cats/data/EitherT.scala index 5f73115532a..7dc39d170a2 100644 --- a/core/src/main/scala/cats/data/EitherT.scala +++ b/core/src/main/scala/cats/data/EitherT.scala @@ -205,6 +205,9 @@ final case class EitherT[F[_], A, B](value: F[Either[A, B]]) { def toValidatedNel(implicit F: Functor[F]): F[ValidatedNel[A, B]] = F.map(value)(_.toValidatedNel) + def toValidatedNec(implicit F: Functor[F]): F[ValidatedNec[A, B]] = + F.map(value)(_.toValidatedNec) + /** Run this value as a `[[Validated]]` against the function and convert it back to an `[[EitherT]]`. * * The [[Applicative]] instance for `EitherT` "fails fast" - it is often useful to "momentarily" have @@ -274,6 +277,12 @@ final case class EitherT[F[_], A, B](value: F[Either[A, B]]) { */ def toNestedValidatedNel(implicit F: Functor[F]): Nested[F, ValidatedNel[A, ?], B] = Nested[F, ValidatedNel[A, ?], B](F.map(value)(_.toValidatedNel)) + + /** + * Transform this `EitherT[F, A, B]` into a `[[Nested]][F, ValidatedNec[A, ?], B]`. + */ + def toNestedValidatedNec(implicit F: Functor[F]): Nested[F, ValidatedNec[A, ?], B] = + Nested[F, ValidatedNec[A, ?], B](F.map(value)(_.toValidatedNec)) } object EitherT extends EitherTInstances { diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index 4cc8d56884a..6ea24189c68 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -314,7 +314,7 @@ sealed abstract class Validated[+E, +A] extends Product with Serializable { } } -object Validated extends ValidatedInstances with ValidatedFunctions{ +object Validated extends ValidatedInstances with ValidatedFunctions with ValidatedFunctionsBinCompat0 { final case class Valid[+A](a: A) extends Validated[Nothing, A] final case class Invalid[+E](e: E) extends Validated[E, Nothing] @@ -602,3 +602,38 @@ private[data] trait ValidatedFunctions { final def condNel[A, B](test: Boolean, b: => B, a: => A): ValidatedNel[A, B] = if (test) validNel(b) else invalidNel(a) } + +private[data] trait ValidatedFunctionsBinCompat0 { + + + /** + * Converts a `B` to a `ValidatedNec[A, B]`. + * + * For example: + * {{{ + * scala> Validated.validNec[IllegalArgumentException, String]("Hello world") + * res0: ValidatedNec[IllegalArgumentException, String] = Valid(Hello world) + * }}} + */ + def validNec[A, B](b: B): ValidatedNec[A, B] = Validated.Valid(b) + + + + /** + * Converts an `A` to a `ValidatedNec[A, B]`. + * + * For example: + * {{{ + * scala> Validated.invalidNec[IllegalArgumentException, String](new IllegalArgumentException("Argument is nonzero")) + * res0: ValidatedNec[IllegalArgumentException, String] = Invalid(Chain(java.lang.IllegalArgumentException: Argument is nonzero)) + * }}} + */ + def invalidNec[A, B](a: A): ValidatedNec[A, B] = Validated.Invalid(NonEmptyChain.one(a)) + + /** + * If the condition is satisfied, return the given `B` as valid NEC, + * otherwise return the given `A` as invalid NEC. + */ + final def condNec[A, B](test: Boolean, b: => B, a: => A): ValidatedNec[A, B] = + if (test) validNec(b) else invalidNec(a) +} diff --git a/core/src/main/scala/cats/data/package.scala b/core/src/main/scala/cats/data/package.scala index 5333a66b415..218300dbc00 100644 --- a/core/src/main/scala/cats/data/package.scala +++ b/core/src/main/scala/cats/data/package.scala @@ -5,6 +5,9 @@ package object data { type ValidatedNel[+E, +A] = Validated[NonEmptyList[E], A] type IorNel[+B, +A] = Ior[NonEmptyList[B], A] type EitherNel[+E, +A] = Either[NonEmptyList[E], A] + type ValidatedNec[+E, +A] = Validated[NonEmptyChain[E], A] + type IorNec[+B, +A] = Ior[NonEmptyChain[B], A] + type EitherNec[+E, +A] = Either[NonEmptyChain[E], A] def NonEmptyStream[A](head: A, tail: Stream[A] = Stream.empty): NonEmptyStream[A] = OneAnd(head, tail) diff --git a/core/src/main/scala/cats/syntax/all.scala b/core/src/main/scala/cats/syntax/all.scala index ebee850312e..78a67f51df3 100644 --- a/core/src/main/scala/cats/syntax/all.scala +++ b/core/src/main/scala/cats/syntax/all.scala @@ -72,3 +72,6 @@ trait AllSyntaxBinCompat1 trait AllSyntaxBinCompat2 extends ParallelTraverseSyntax + with EitherSyntaxBinCompat0 + with ListSyntaxBinCompat0 + with ValidatedSyntaxBincompat0 diff --git a/core/src/main/scala/cats/syntax/either.scala b/core/src/main/scala/cats/syntax/either.scala index 40bd310652a..78fcfc78632 100644 --- a/core/src/main/scala/cats/syntax/either.scala +++ b/core/src/main/scala/cats/syntax/either.scala @@ -1,7 +1,8 @@ package cats package syntax -import cats.data.{EitherT, Ior, NonEmptyList, Validated, ValidatedNel} +import cats.data._ + import scala.reflect.ClassTag import scala.util.{Failure, Success, Try} import EitherSyntax._ @@ -374,6 +375,49 @@ final class EitherIdOps[A](val obj: A) extends AnyVal { } +trait EitherSyntaxBinCompat0 { + implicit final def catsSyntaxEitherBinCompat0[A, B](eab: Either[A, B]): EitherOpsBinCompat0[A, B] = + new EitherOpsBinCompat0(eab) + + implicit final def catsSyntaxEitherIdBinCompat0[A](a: A): EitherIdOpsBinCompat0[A] = + new EitherIdOpsBinCompat0(a) +} + +final class EitherIdOpsBinCompat0[A](val value: A) extends AnyVal { + /** + * Wrap a value to a left EitherNec + * + * For example: + * {{{ + * scala> import cats.implicits._, cats.data.NonEmptyChain + * scala> "Err".leftNec[Int] + * res0: Either[NonEmptyChain[String], Int] = Left(Chain(Err)) + * }}} + */ + def leftNec[B]: Either[NonEmptyChain[A], B] = Left(NonEmptyChain.one(value)) + + /** + * Wrap a value to a right EitherNec + * + * For example: + * {{{ + * scala> import cats.implicits._, cats.data.NonEmptyChain + * scala> 1.rightNec[String] + * res0: Either[NonEmptyChain[String], Int] = Right(1) + * }}} + */ + def rightNec[B]: Either[NonEmptyChain[B], A] = Right(value) +} + +final class EitherOpsBinCompat0[A, B](val value: Either[A, B]) extends AnyVal { + /** Returns a [[cats.data.ValidatedNec]] representation of this disjunction with the `Left` value + * as a single element on the `Invalid` side of the [[cats.data.NonEmptyList]]. */ + def toValidatedNec: ValidatedNec[A, B] = value match { + case Left(a) => Validated.invalidNec(a) + case Right(b) => Validated.valid(b) + } +} + /** Convenience methods to use `Either` syntax inside `Either` syntax definitions. */ private[cats] object EitherUtil { def leftCast[A, B, C](right: Right[A, B]): Either[C, B] = diff --git a/core/src/main/scala/cats/syntax/list.scala b/core/src/main/scala/cats/syntax/list.scala index 710ab5a2fb4..820c9061682 100644 --- a/core/src/main/scala/cats/syntax/list.scala +++ b/core/src/main/scala/cats/syntax/list.scala @@ -2,7 +2,7 @@ package cats package syntax import scala.collection.immutable.SortedMap -import cats.data.NonEmptyList +import cats.data.{NonEmptyChain, NonEmptyList} trait ListSyntax { implicit final def catsSyntaxList[A](la: List[A]): ListOps[A] = new ListOps(la) @@ -49,3 +49,30 @@ final class ListOps[A](val la: List[A]) extends AnyVal { toNel.fold(SortedMap.empty[B, NonEmptyList[A]])(_.groupBy(f)) } } + +trait ListSyntaxBinCompat0 { + implicit final def catsSyntaxListBinCompat0[A](la: List[A]): ListOpsBinCompat0[A] = new ListOpsBinCompat0(la) +} + +final class ListOpsBinCompat0[A](val la: List[A]) extends AnyVal { + + /** + * Groups elements inside this `List` according to the `Order` of the keys + * produced by the given mapping function. + * + * {{{ + * scala> import cats.data.NonEmptyChain + * scala> import scala.collection.immutable.SortedMap + * scala> import cats.implicits._ + * + * scala> val list = List(12, -2, 3, -5) + * + * scala> list.groupByNec(_ >= 0) + * res0: SortedMap[Boolean, NonEmptyChain[Int]] = Map(false -> Chain(-2, -5), true -> Chain(12, 3)) + * }}} + */ + def groupByNec[B](f: A => B)(implicit B: Order[B]): SortedMap[B, NonEmptyChain[A]] = { + implicit val ordering = B.toOrdering + NonEmptyChain.fromSeq(la).fold(SortedMap.empty[B, NonEmptyChain[A]])(_.groupBy(f).toSortedMap) + } +} diff --git a/core/src/main/scala/cats/syntax/package.scala b/core/src/main/scala/cats/syntax/package.scala index cf16200fff7..03b4413f22f 100644 --- a/core/src/main/scala/cats/syntax/package.scala +++ b/core/src/main/scala/cats/syntax/package.scala @@ -23,7 +23,7 @@ package object syntax { object contravariant extends ContravariantSyntax object contravariantSemigroupal extends ContravariantSemigroupalSyntax object contravariantMonoidal extends ContravariantMonoidalSyntax - object either extends EitherSyntax + object either extends EitherSyntax with EitherSyntaxBinCompat0 object eq extends EqSyntax object flatMap extends FlatMapSyntax object foldable extends FoldableSyntax @@ -31,7 +31,7 @@ package object syntax { object group extends GroupSyntax object invariant extends InvariantSyntax object ior extends IorSyntax - object list extends ListSyntax + object list extends ListSyntax with ListSyntaxBinCompat0 object monad extends MonadSyntax object monadError extends MonadErrorSyntax object monoid extends MonoidSyntax @@ -51,7 +51,7 @@ package object syntax { object traverse extends TraverseSyntax object nonEmptyTraverse extends NonEmptyTraverseSyntax object unorderedTraverse extends UnorderedTraverseSyntax - object validated extends ValidatedSyntax with ValidatedExtensionSyntax + object validated extends ValidatedSyntax with ValidatedExtensionSyntax with ValidatedSyntaxBincompat0 object vector extends VectorSyntax object writer extends WriterSyntax object set extends SetSyntax diff --git a/core/src/main/scala/cats/syntax/validated.scala b/core/src/main/scala/cats/syntax/validated.scala index fef761d8176..8b12f636b25 100644 --- a/core/src/main/scala/cats/syntax/validated.scala +++ b/core/src/main/scala/cats/syntax/validated.scala @@ -1,7 +1,7 @@ package cats package syntax -import cats.data.{ Validated, ValidatedNel } +import cats.data.{Validated, ValidatedNec, ValidatedNel} trait ValidatedSyntax { implicit final def catsSyntaxValidatedId[A](a: A): ValidatedIdSyntax[A] = new ValidatedIdSyntax(a) @@ -23,3 +23,34 @@ final class ValidatedExtension[E, A](val self: Validated[E, A]) extends AnyVal { def liftTo[F[_]](implicit F: ApplicativeError[F, E]): F[A] = new ApplicativeErrorExtensionOps(F).fromValidated(self) } + +trait ValidatedSyntaxBincompat0 { + implicit final def catsSyntaxValidatedIdBinCompat0[A](a: A): ValidatedIdOpsBinCompat0[A] = + new ValidatedIdOpsBinCompat0(a) +} + +final class ValidatedIdOpsBinCompat0[A](val a: A) extends AnyVal { + /** + * Wrap a value to a valid ValidatedNec + * + * For example: + * {{{ + * scala> import cats.implicits._, cats.data._ + * scala> 1.validNec[String] + * res0: Validated[NonEmptyChain[String], Int] = Valid(1) + * }}} + */ + def validNec[B]: ValidatedNec[B, A] = Validated.Valid(a) + + /** + * Wrap a value to an invalid ValidatedNec + * + * For example: + * {{{ + * scala> import cats.implicits._, cats.data._ + * scala> "Err".invalidNec[Int] + * res0: Validated[NonEmptyChain[String], Int] = Invalid(Chain(Err)) + * }}} + */ + def invalidNec[B]: ValidatedNec[A, B] = Validated.invalidNec(a) +} diff --git a/tests/src/test/scala/cats/tests/EitherSuite.scala b/tests/src/test/scala/cats/tests/EitherSuite.scala index 4d3a791ddda..0e3d8586b66 100644 --- a/tests/src/test/scala/cats/tests/EitherSuite.scala +++ b/tests/src/test/scala/cats/tests/EitherSuite.scala @@ -223,6 +223,7 @@ class EitherSuite extends CatsSuite { x.isLeft should === (x.toList.isEmpty) x.isLeft should === (x.toValidated.isInvalid) x.isLeft should === (x.toValidatedNel.isInvalid) + x.isLeft should === (x.toValidatedNec.isInvalid) Option(x.isLeft) should === (x.toEitherT[Option].isLeft) } } diff --git a/tests/src/test/scala/cats/tests/EitherTSuite.scala b/tests/src/test/scala/cats/tests/EitherTSuite.scala index 4e7f198082a..e87400d1c97 100644 --- a/tests/src/test/scala/cats/tests/EitherTSuite.scala +++ b/tests/src/test/scala/cats/tests/EitherTSuite.scala @@ -141,6 +141,12 @@ class EitherTSuite extends CatsSuite { } } + test("toValidatedNec") { + forAll { (eithert: EitherT[List, String, Int]) => + eithert.toValidatedNec.map(_.toEither.leftMap(_.head)) should === (eithert.value) + } + } + test("toNested") { forAll { (eithert: EitherT[List, String, Int]) => eithert.toNested.value should === (eithert.value) @@ -159,6 +165,12 @@ class EitherTSuite extends CatsSuite { } } + test("toNestedValidatedNec") { + forAll { (eithert: EitherT[List, String, Int]) => + eithert.toNestedValidatedNec.value should === (eithert.value.map(_.toValidatedNec)) + } + } + test("withValidated") { forAll { (eithert: EitherT[List, String, Int], f: String => Char, g: Int => Double) => eithert.withValidated(_.bimap(f, g)) should === (eithert.bimap(f, g)) diff --git a/tests/src/test/scala/cats/tests/SyntaxSuite.scala b/tests/src/test/scala/cats/tests/SyntaxSuite.scala index 0e3350eeb21..2e8a21ff0ed 100644 --- a/tests/src/test/scala/cats/tests/SyntaxSuite.scala +++ b/tests/src/test/scala/cats/tests/SyntaxSuite.scala @@ -4,7 +4,7 @@ package tests import scala.collection.immutable.SortedSet import scala.collection.immutable.SortedMap import cats.arrow.Compose -import cats.data.{Binested, Nested, NonEmptyList, NonEmptySet} +import cats.data.{Binested, Nested, NonEmptyChain, NonEmptyList, NonEmptySet} import cats.instances.AllInstances import cats.syntax.{AllSyntax, AllSyntaxBinCompat} @@ -377,5 +377,12 @@ object SyntaxSuite extends AllSyntaxBinCompat with AllInstances with AllSyntax { val grouped: SortedMap[B, NonEmptyList[A]] = list.groupByNel(f) } + def testNonEmptyChain[A, B: Order] : Unit = { + val f = mock[A => B] + val list = mock[List[A]] + + val grouped: SortedMap[B, NonEmptyChain[A]] = list.groupByNec(f) + } + } diff --git a/tests/src/test/scala/cats/tests/ValidatedSuite.scala b/tests/src/test/scala/cats/tests/ValidatedSuite.scala index 82a779acec5..0cf40334f3c 100644 --- a/tests/src/test/scala/cats/tests/ValidatedSuite.scala +++ b/tests/src/test/scala/cats/tests/ValidatedSuite.scala @@ -280,6 +280,12 @@ class ValidatedSuite extends CatsSuite { } } + test("condNec consistent with Either.cond + toValidatedNec") { + forAll { (cond: Boolean, s: String, i: Int) => + Validated.condNec(cond, s, i) should === (Either.cond(cond, s, i).toValidatedNec) + } + } + test("liftTo consistent with direct to Option") { forAll { (v: Validated[Unit, Int]) => v.liftTo[Option] shouldBe v.toOption From 6ac776c0ddb4d2be89e04f278dd35ad43c0260fe Mon Sep 17 00:00:00 2001 From: Song Kun Date: Sun, 2 Sep 2018 00:53:32 +0800 Subject: [PATCH 14/14] remove unnecessary-semicolons (#2451) --- core/src/main/scala/cats/data/Chain.scala | 2 +- core/src/main/scala/cats/instances/sortedMap.scala | 2 +- kernel/src/main/scala/cats/kernel/instances/map.scala | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala/cats/data/Chain.scala b/core/src/main/scala/cats/data/Chain.scala index 916c79f131e..361f0e31b67 100644 --- a/core/src/main/scala/cats/data/Chain.scala +++ b/core/src/main/scala/cats/data/Chain.scala @@ -276,7 +276,7 @@ sealed abstract class Chain[+A] { c match { case Singleton(a) => val b = f(a) - if (b) return (); + if (b) return () c = if (rights.isEmpty) Empty else rights.reduceLeft((x, y) => Append(y, x)) diff --git a/core/src/main/scala/cats/instances/sortedMap.scala b/core/src/main/scala/cats/instances/sortedMap.scala index 5a25a9c0ca0..df27ea937c8 100644 --- a/core/src/main/scala/cats/instances/sortedMap.scala +++ b/core/src/main/scala/cats/instances/sortedMap.scala @@ -120,7 +120,7 @@ class SortedMapHash[K, V](implicit V: Hash[V], O: Order[K], K: Hash[K]) extends import scala.util.hashing.MurmurHash3._ def hash(x: SortedMap[K, V]): Int = { var a, b, n = 0 - var c = 1; + var c = 1 x foreach { case (k, v) => val h = StaticMethods.product2Hash(K.hash(k), V.hash(v)) a += h diff --git a/kernel/src/main/scala/cats/kernel/instances/map.scala b/kernel/src/main/scala/cats/kernel/instances/map.scala index 3ad5db23da7..1831ffb9913 100644 --- a/kernel/src/main/scala/cats/kernel/instances/map.scala +++ b/kernel/src/main/scala/cats/kernel/instances/map.scala @@ -26,7 +26,7 @@ class MapHash[K, V](implicit V: Hash[V]) extends MapEq[K, V]()(V) with Hash[Map[ import scala.util.hashing.MurmurHash3._ def hash(x: Map[K, V]): Int = { var a, b, n = 0 - var c = 1; + var c = 1 x foreach { case (k, v) => // use the default hash on keys because that's what Scala's Map does val h = StaticMethods.product2Hash(k.hashCode(), V.hash(v))