diff --git a/build.sbt b/build.sbt index dd4f230ab8..74d9790e26 100644 --- a/build.sbt +++ b/build.sbt @@ -52,6 +52,7 @@ def scalaVersionSpecificFolders(srcName: String, srcBaseDir: java.io.File, scala lazy val commonSettings = Seq( crossScalaVersions := (crossScalaVersionsFromTravis in Global).value, + scalaVersion := crossScalaVersions.value.find(_.contains("2.13")).get, scalacOptions ++= commonScalacOptions(scalaVersion.value), Compile / unmanagedSourceDirectories ++= scalaVersionSpecificFolders("main", baseDirectory.value, scalaVersion.value), Test / unmanagedSourceDirectories ++= scalaVersionSpecificFolders("test", baseDirectory.value, scalaVersion.value), diff --git a/core/src/main/scala/cats/instances/all.scala b/core/src/main/scala/cats/instances/all.scala index ccc50d259b..d489fc768f 100644 --- a/core/src/main/scala/cats/instances/all.scala +++ b/core/src/main/scala/cats/instances/all.scala @@ -25,7 +25,7 @@ trait AllInstances with SetInstances with SortedMapInstances with SortedSetInstances - with StreamInstances + with LazyListInstances with StringInstances with SymbolInstances with TryInstances diff --git a/core/src/main/scala/cats/instances/lazyList.scala b/core/src/main/scala/cats/instances/lazyList.scala new file mode 100644 index 0000000000..e85988c030 --- /dev/null +++ b/core/src/main/scala/cats/instances/lazyList.scala @@ -0,0 +1,185 @@ +package cats +package instances +import cats.syntax.show._ + +import scala.annotation.tailrec + +trait LazyListInstances extends cats.kernel.instances.LazyListInstances { + implicit val catsStdInstancesForLazyList + : Traverse[LazyList] with Alternative[LazyList] with Monad[LazyList] with CoflatMap[LazyList] = + new Traverse[LazyList] with Alternative[LazyList] with Monad[LazyList] with CoflatMap[LazyList] { + + def empty[A]: LazyList[A] = LazyList.empty + + def combineK[A](x: LazyList[A], y: LazyList[A]): LazyList[A] = x lazyAppendedAll y + + def pure[A](x: A): LazyList[A] = LazyList(x) + + override def map[A, B](fa: LazyList[A])(f: A => B): LazyList[B] = + fa.map(f) + + def flatMap[A, B](fa: LazyList[A])(f: A => LazyList[B]): LazyList[B] = + fa.flatMap(f) + + override def map2[A, B, Z](fa: LazyList[A], fb: LazyList[B])(f: (A, B) => Z): LazyList[Z] = + if (fb.isEmpty) LazyList.empty // do O(1) work if fb is empty + else fa.flatMap(a => fb.map(b => f(a, b))) // already O(1) if fa is empty + + override def map2Eval[A, B, Z](fa: LazyList[A], fb: Eval[LazyList[B]])(f: (A, B) => Z): Eval[LazyList[Z]] = + if (fa.isEmpty) Eval.now(LazyList.empty) // no need to evaluate fb + else fb.map(fb => map2(fa, fb)(f)) + + def coflatMap[A, B](fa: LazyList[A])(f: LazyList[A] => B): LazyList[B] = + fa.tails.to(LazyList).init.map(f) + + def foldLeft[A, B](fa: LazyList[A], b: B)(f: (B, A) => B): B = + fa.foldLeft(b)(f) + + def foldRight[A, B](fa: LazyList[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = + Now(fa).flatMap { s => + // Note that we don't use pattern matching to deconstruct the + // stream, since that would needlessly force the tail. + if (s.isEmpty) lb else f(s.head, Eval.defer(foldRight(s.tail, lb)(f))) + } + + override def foldMap[A, B](fa: LazyList[A])(f: A => B)(implicit B: Monoid[B]): B = + B.combineAll(fa.iterator.map(f)) + + def traverse[G[_], A, B](fa: LazyList[A])(f: A => G[B])(implicit G: Applicative[G]): G[LazyList[B]] = + // We use foldRight to avoid possible stack overflows. Since + // we don't want to return a Eval[_] instance, we call .value + // at the end. + foldRight(fa, Always(G.pure(LazyList.empty[B]))) { (a, lgsb) => + G.map2Eval(f(a), lgsb)(_ #:: _) + }.value + + override def mapWithIndex[A, B](fa: LazyList[A])(f: (A, Int) => B): LazyList[B] = + fa.zipWithIndex.map(ai => f(ai._1, ai._2)) + + override def zipWithIndex[A](fa: LazyList[A]): LazyList[(A, Int)] = + fa.zipWithIndex + + def tailRecM[A, B](a: A)(fn: A => LazyList[Either[A, B]]): LazyList[B] = { + val it: Iterator[B] = new Iterator[B] { + var stack: LazyList[Either[A, B]] = fn(a) + var state: Either[Unit, Option[B]] = Left(()) + + @tailrec + def advance(): Unit = stack match { + case Right(b) #:: tail => + stack = tail + state = Right(Some(b)) + case Left(a) #:: tail => + stack = (fn(a) #::: tail).force + advance() + case empty => + state = Right(None) + } + + @tailrec + def hasNext: Boolean = state match { + case Left(()) => + advance() + hasNext + case Right(o) => + o.isDefined + } + + @tailrec + def next(): B = state match { + case Left(()) => + advance() + next() + case Right(o) => + val b = o.get + advance() + b + } + } + + it.to(LazyList) + } + + override def exists[A](fa: LazyList[A])(p: A => Boolean): Boolean = + fa.exists(p) + + override def forall[A](fa: LazyList[A])(p: A => Boolean): Boolean = + fa.forall(p) + + override def get[A](fa: LazyList[A])(idx: Long): Option[A] = { + @tailrec + def go(idx: Long, s: LazyList[A]): Option[A] = + s match { + case h #:: tail => + if (idx == 0L) Some(h) else go(idx - 1L, tail) + case _ => None + } + if (idx < 0L) None else go(idx, fa) + } + + override def isEmpty[A](fa: LazyList[A]): Boolean = fa.isEmpty + + override def foldM[G[_], A, B](fa: LazyList[A], z: B)(f: (B, A) => G[B])(implicit G: Monad[G]): G[B] = { + def step(in: (LazyList[A], B)): G[Either[(LazyList[A], B), B]] = { + val (s, b) = in + if (s.isEmpty) + G.pure(Right(b)) + else + G.map(f(b, s.head)) { bnext => + Left((s.tail, bnext)) + } + } + + G.tailRecM((fa, z))(step) + } + + override def fold[A](fa: LazyList[A])(implicit A: Monoid[A]): A = A.combineAll(fa) + + override def toList[A](fa: LazyList[A]): List[A] = fa.toList + + override def reduceLeftOption[A](fa: LazyList[A])(f: (A, A) => A): Option[A] = + fa.reduceLeftOption(f) + + override def find[A](fa: LazyList[A])(f: A => Boolean): Option[A] = fa.find(f) + + override def algebra[A]: Monoid[LazyList[A]] = new kernel.instances.LazyListMonoid[A] + + override def collectFirst[A, B](fa: LazyList[A])(pf: PartialFunction[A, B]): Option[B] = fa.collectFirst(pf) + + override def collectFirstSome[A, B](fa: LazyList[A])(f: A => Option[B]): Option[B] = + fa.collectFirst(Function.unlift(f)) + } + + implicit def catsStdShowForStream[A: Show]: Show[LazyList[A]] = + new Show[LazyList[A]] { + def show(fa: LazyList[A]): String = if (fa.isEmpty) "LazyList()" else s"LazyList(${fa.head.show}, ?)" + } +} + +trait StreamInstancesBinCompat0 { + implicit val catsStdTraverseFilterForLazyList: TraverseFilter[LazyList] = new TraverseFilter[LazyList] { + val traverse: Traverse[LazyList] = cats.instances.stream.catsStdInstancesForLazyList + + override def mapFilter[A, B](fa: LazyList[A])(f: (A) => Option[B]): LazyList[B] = + fa.collect(Function.unlift(f)) + + override def filter[A](fa: LazyList[A])(f: (A) => Boolean): LazyList[A] = fa.filter(f) + + override def collect[A, B](fa: LazyList[A])(f: PartialFunction[A, B]): LazyList[B] = fa.collect(f) + + override def flattenOption[A](fa: LazyList[Option[A]]): LazyList[A] = fa.flatten + + def traverseFilter[G[_], A, B](fa: LazyList[A])(f: (A) => G[Option[B]])(implicit G: Applicative[G]): G[LazyList[B]] = + fa.foldRight(Eval.now(G.pure(LazyList.empty[B])))( + (x, xse) => G.map2Eval(f(x), xse)((i, o) => i.fold(o)(_ +: o)) + ) + .value + + override def filterA[G[_], A](fa: LazyList[A])(f: (A) => G[Boolean])(implicit G: Applicative[G]): G[LazyList[A]] = + fa.foldRight(Eval.now(G.pure(LazyList.empty[A])))( + (x, xse) => G.map2Eval(f(x), xse)((b, as) => if (b) x +: as else as) + ) + .value + + } +} diff --git a/core/src/main/scala/cats/instances/package.scala b/core/src/main/scala/cats/instances/package.scala index c171490fbb..ffcfad1b39 100644 --- a/core/src/main/scala/cats/instances/package.scala +++ b/core/src/main/scala/cats/instances/package.scala @@ -40,7 +40,7 @@ package object instances { object short extends ShortInstances object sortedMap extends SortedMapInstances with SortedMapInstancesBinCompat0 with SortedMapInstancesBinCompat1 object sortedSet extends SortedSetInstances with SortedSetInstancesBinCompat0 - object stream extends StreamInstances with StreamInstancesBinCompat0 + object stream extends LazyListInstances with StreamInstancesBinCompat0 object string extends StringInstances object try_ extends TryInstances object tuple extends TupleInstances with Tuple2InstancesBinCompat0 diff --git a/core/src/main/scala/cats/instances/parallel.scala b/core/src/main/scala/cats/instances/parallel.scala index 3c85561bc1..f8f3c33c87 100644 --- a/core/src/main/scala/cats/instances/parallel.scala +++ b/core/src/main/scala/cats/instances/parallel.scala @@ -69,7 +69,7 @@ trait ParallelInstances extends ParallelInstances1 { implicit def catsStdParallelForZipStream[A]: Parallel[Stream, ZipStream] = new Parallel[Stream, ZipStream] { - def monad: Monad[Stream] = cats.instances.stream.catsStdInstancesForStream + def monad: Monad[Stream] = cats.instances.stream.catsStdInstancesForLazyList def applicative: Applicative[ZipStream] = ZipStream.catsDataAlternativeForZipStream def sequential: ZipStream ~> Stream = diff --git a/core/src/main/scala/cats/instances/stream.scala b/core/src/main/scala/cats/instances/stream.scala deleted file mode 100644 index 5ac853fb25..0000000000 --- a/core/src/main/scala/cats/instances/stream.scala +++ /dev/null @@ -1,187 +0,0 @@ -package cats -package instances -import kernel.compat.Stream -import compat.StreamOps._ -import cats.syntax.show._ - -import scala.annotation.tailrec - -trait StreamInstances extends cats.kernel.instances.StreamInstances { - implicit val catsStdInstancesForStream - : Traverse[Stream] with Alternative[Stream] with Monad[Stream] with CoflatMap[Stream] = - new Traverse[Stream] with Alternative[Stream] with Monad[Stream] with CoflatMap[Stream] { - - def empty[A]: Stream[A] = emptyStream - - def combineK[A](x: Stream[A], y: Stream[A]): Stream[A] = x lazyAppendedAll y - - def pure[A](x: A): Stream[A] = Stream(x) - - override def map[A, B](fa: Stream[A])(f: A => B): Stream[B] = - fa.map(f) - - def flatMap[A, B](fa: Stream[A])(f: A => Stream[B]): Stream[B] = - fa.flatMap(f) - - override def map2[A, B, Z](fa: Stream[A], fb: Stream[B])(f: (A, B) => Z): Stream[Z] = - if (fb.isEmpty) Stream.empty // do O(1) work if fb is empty - else fa.flatMap(a => fb.map(b => f(a, b))) // already O(1) if fa is empty - - override def map2Eval[A, B, Z](fa: Stream[A], fb: Eval[Stream[B]])(f: (A, B) => Z): Eval[Stream[Z]] = - if (fa.isEmpty) Eval.now(Stream.empty) // no need to evaluate fb - else fb.map(fb => map2(fa, fb)(f)) - - def coflatMap[A, B](fa: Stream[A])(f: Stream[A] => B): Stream[B] = - toStream(fa.tails).init.map(f) - - def foldLeft[A, B](fa: Stream[A], b: B)(f: (B, A) => B): B = - fa.foldLeft(b)(f) - - def foldRight[A, B](fa: Stream[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = - Now(fa).flatMap { s => - // Note that we don't use pattern matching to deconstruct the - // stream, since that would needlessly force the tail. - if (s.isEmpty) lb else f(s.head, Eval.defer(foldRight(s.tail, lb)(f))) - } - - override def foldMap[A, B](fa: Stream[A])(f: A => B)(implicit B: Monoid[B]): B = - B.combineAll(fa.iterator.map(f)) - - def traverse[G[_], A, B](fa: Stream[A])(f: A => G[B])(implicit G: Applicative[G]): G[Stream[B]] = - // We use foldRight to avoid possible stack overflows. Since - // we don't want to return a Eval[_] instance, we call .value - // at the end. - foldRight(fa, Always(G.pure(Stream.empty[B]))) { (a, lgsb) => - G.map2Eval(f(a), lgsb)(_ #:: _) - }.value - - override def mapWithIndex[A, B](fa: Stream[A])(f: (A, Int) => B): Stream[B] = - fa.zipWithIndex.map(ai => f(ai._1, ai._2)) - - override def zipWithIndex[A](fa: Stream[A]): Stream[(A, Int)] = - fa.zipWithIndex - - def tailRecM[A, B](a: A)(fn: A => Stream[Either[A, B]]): Stream[B] = { - val it: Iterator[B] = new Iterator[B] { - var stack: Stream[Either[A, B]] = fn(a) - var state: Either[Unit, Option[B]] = Left(()) - - @tailrec - def advance(): Unit = stack match { - case Right(b) #:: tail => - stack = tail - state = Right(Some(b)) - case Left(a) #:: tail => - stack = (fn(a) #::: tail).force - advance() - case empty => - state = Right(None) - } - - @tailrec - def hasNext: Boolean = state match { - case Left(()) => - advance() - hasNext - case Right(o) => - o.isDefined - } - - @tailrec - def next(): B = state match { - case Left(()) => - advance() - next() - case Right(o) => - val b = o.get - advance() - b - } - } - - toStream(it) - } - - override def exists[A](fa: Stream[A])(p: A => Boolean): Boolean = - fa.exists(p) - - override def forall[A](fa: Stream[A])(p: A => Boolean): Boolean = - fa.forall(p) - - override def get[A](fa: Stream[A])(idx: Long): Option[A] = { - @tailrec - def go(idx: Long, s: Stream[A]): Option[A] = - s match { - case h #:: tail => - if (idx == 0L) Some(h) else go(idx - 1L, tail) - case _ => None - } - if (idx < 0L) None else go(idx, fa) - } - - override def isEmpty[A](fa: Stream[A]): Boolean = fa.isEmpty - - override def foldM[G[_], A, B](fa: Stream[A], z: B)(f: (B, A) => G[B])(implicit G: Monad[G]): G[B] = { - def step(in: (Stream[A], B)): G[Either[(Stream[A], B), B]] = { - val (s, b) = in - if (s.isEmpty) - G.pure(Right(b)) - else - G.map(f(b, s.head)) { bnext => - Left((s.tail, bnext)) - } - } - - G.tailRecM((fa, z))(step) - } - - override def fold[A](fa: Stream[A])(implicit A: Monoid[A]): A = A.combineAll(fa) - - override def toList[A](fa: Stream[A]): List[A] = fa.toList - - override def reduceLeftOption[A](fa: Stream[A])(f: (A, A) => A): Option[A] = - fa.reduceLeftOption(f) - - override def find[A](fa: Stream[A])(f: A => Boolean): Option[A] = fa.find(f) - - override def algebra[A]: Monoid[Stream[A]] = new kernel.instances.StreamMonoid[A] - - override def collectFirst[A, B](fa: Stream[A])(pf: PartialFunction[A, B]): Option[B] = fa.collectFirst(pf) - - override def collectFirstSome[A, B](fa: Stream[A])(f: A => Option[B]): Option[B] = - fa.collectFirst(Function.unlift(f)) - } - - implicit def catsStdShowForStream[A: Show]: Show[Stream[A]] = - new Show[Stream[A]] { - def show(fa: Stream[A]): String = if (fa.isEmpty) s"$streamString()" else s"$streamString(${fa.head.show}, ?)" - } -} - -trait StreamInstancesBinCompat0 { - implicit val catsStdTraverseFilterForStream: TraverseFilter[Stream] = new TraverseFilter[Stream] { - val traverse: Traverse[Stream] = cats.instances.stream.catsStdInstancesForStream - - override def mapFilter[A, B](fa: Stream[A])(f: (A) => Option[B]): Stream[B] = - fa.collect(Function.unlift(f)) - - override def filter[A](fa: Stream[A])(f: (A) => Boolean): Stream[A] = fa.filter(f) - - override def collect[A, B](fa: Stream[A])(f: PartialFunction[A, B]): Stream[B] = fa.collect(f) - - override def flattenOption[A](fa: Stream[Option[A]]): Stream[A] = fa.flatten - - def traverseFilter[G[_], A, B](fa: Stream[A])(f: (A) => G[Option[B]])(implicit G: Applicative[G]): G[Stream[B]] = - fa.foldRight(Eval.now(G.pure(emptyStream[B])))( - (x, xse) => G.map2Eval(f(x), xse)((i, o) => i.fold(o)(_ +: o)) - ) - .value - - override def filterA[G[_], A](fa: Stream[A])(f: (A) => G[Boolean])(implicit G: Applicative[G]): G[Stream[A]] = - fa.foldRight(Eval.now(G.pure(emptyStream[A])))( - (x, xse) => G.map2Eval(f(x), xse)((b, as) => if (b) x +: as else as) - ) - .value - - } -} diff --git a/kernel/src/main/scala/cats/kernel/instances/AllInstances.scala b/kernel/src/main/scala/cats/kernel/instances/AllInstances.scala index d66ba81365..6eb5a5e8dd 100644 --- a/kernel/src/main/scala/cats/kernel/instances/AllInstances.scala +++ b/kernel/src/main/scala/cats/kernel/instances/AllInstances.scala @@ -25,7 +25,7 @@ trait AllInstances with QueueInstances with SetInstances with ShortInstances - with StreamInstances + with LazyListInstances with StringInstances with SymbolInstances with TupleInstances diff --git a/kernel/src/main/scala/cats/kernel/instances/StreamInstances.scala b/kernel/src/main/scala/cats/kernel/instances/LazyListInstances.scala similarity index 93% rename from kernel/src/main/scala/cats/kernel/instances/StreamInstances.scala rename to kernel/src/main/scala/cats/kernel/instances/LazyListInstances.scala index c683f6f3ac..4930aabcb8 100644 --- a/kernel/src/main/scala/cats/kernel/instances/StreamInstances.scala +++ b/kernel/src/main/scala/cats/kernel/instances/LazyListInstances.scala @@ -2,11 +2,11 @@ package cats.kernel package instances import compat.Stream -trait StreamInstances extends StreamInstances1 { +trait LazyListInstances extends StreamInstances1 { implicit def catsKernelStdOrderForStream[A: Order]: Order[Stream[A]] = new StreamOrder[A] implicit def catsKernelStdMonoidForStream[A]: Monoid[Stream[A]] = - new StreamMonoid[A] + new LazyListMonoid[A] } trait StreamInstances1 extends StreamInstances2 { @@ -44,7 +44,7 @@ class StreamEq[A](implicit ev: Eq[A]) extends Eq[Stream[A]] { else StaticMethods.iteratorEq(xs.iterator, ys.iterator) } -class StreamMonoid[A] extends Monoid[Stream[A]] { +class LazyListMonoid[A] extends Monoid[Stream[A]] { def empty: Stream[A] = Stream.empty def combine(x: Stream[A], y: Stream[A]): Stream[A] = x ++ y diff --git a/kernel/src/main/scala/cats/kernel/instances/stream/package.scala b/kernel/src/main/scala/cats/kernel/instances/stream/package.scala index 53bbfe510d..18a9b3ec1d 100644 --- a/kernel/src/main/scala/cats/kernel/instances/stream/package.scala +++ b/kernel/src/main/scala/cats/kernel/instances/stream/package.scala @@ -1,4 +1,4 @@ package cats.kernel package instances -package object stream extends StreamInstances +package object stream extends LazyListInstances diff --git a/tests/src/test/scala/cats/tests/FoldableSuite.scala b/tests/src/test/scala/cats/tests/FoldableSuite.scala index d28b32a5c1..5f5370d7ce 100644 --- a/tests/src/test/scala/cats/tests/FoldableSuite.scala +++ b/tests/src/test/scala/cats/tests/FoldableSuite.scala @@ -403,10 +403,10 @@ class FoldableSuiteAdditional extends CatsSuite { def foldableStreamWithDefaultImpl = new Foldable[Stream] { def foldLeft[A, B](fa: Stream[A], b: B)(f: (B, A) => B): B = - instances.stream.catsStdInstancesForStream.foldLeft(fa, b)(f) + instances.stream.catsStdInstancesForLazyList.foldLeft(fa, b)(f) def foldRight[A, B](fa: Stream[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = - instances.stream.catsStdInstancesForStream.foldRight(fa, lb)(f) + instances.stream.catsStdInstancesForLazyList.foldRight(fa, lb)(f) } test(".foldLeftM short-circuiting") {