diff --git a/.travis.yml b/.travis.yml index 600debd826..f980fe6ab8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ jdk: - oraclejdk8 scala_version_211: &scala_version_211 2.11.12 -scala_version_212: &scala_version_212 2.12.7 +scala_version_212: &scala_version_212 2.12.8 scala_version_213: &scala_version_213 2.13.0 before_install: diff --git a/build.sbt b/build.sbt index 12c1d4bbb2..8ee90fe25c 100644 --- a/build.sbt +++ b/build.sbt @@ -26,6 +26,16 @@ val isTravisBuild = settingKey[Boolean]("Flag indicating whether the current bui val crossScalaVersionsFromTravis = settingKey[Seq[String]]("Scala versions set in .travis.yml as scala_version_XXX") isTravisBuild in Global := sys.env.get("TRAVIS").isDefined +val scalatestVersion = "3.1.0-SNAP13" + +val scalatestplusScalaCheckVersion = "1.0.0-SNAP8" + +val scalaCheckVersion = "1.14.0" + +val disciplineVersion = "0.12.0-M3" + +val kindProjectorVersion = "0.10.3" + crossScalaVersionsFromTravis in Global := { val manifest = (baseDirectory in ThisBuild).value / ".travis.yml" import collection.JavaConverters._ @@ -40,7 +50,8 @@ crossScalaVersionsFromTravis in Global := { def scalaVersionSpecificFolders(srcName: String, srcBaseDir: java.io.File, scalaVersion: String) = { def extraDirs(suffix: String) = - CrossType.Pure.sharedSrcDir(srcBaseDir, "main").toList.map(f => file(f.getPath + suffix)) + List(CrossType.Pure, CrossType.Full) + .flatMap(_.sharedSrcDir(srcBaseDir, srcName).toList.map(f => file(f.getPath + suffix))) CrossVersion.partialVersion(scalaVersion) match { case Some((2, y)) if y <= 12 => extraDirs("-2.12-") @@ -49,9 +60,12 @@ def scalaVersionSpecificFolders(srcName: String, srcBaseDir: java.io.File, scala case _ => Nil } } - -lazy val commonSettings = Seq( +lazy val commonScalaVersionSettings = Seq( crossScalaVersions := (crossScalaVersionsFromTravis in Global).value, + scalaVersion := crossScalaVersions.value.find(_.contains("2.12")).get +) + +lazy val commonSettings = commonScalaVersionSettings ++ Seq( scalacOptions ++= commonScalacOptions(scalaVersion.value), Compile / unmanagedSourceDirectories ++= scalaVersionSpecificFolders("main", baseDirectory.value, scalaVersion.value), Test / unmanagedSourceDirectories ++= scalaVersionSpecificFolders("test", baseDirectory.value, scalaVersion.value), @@ -83,7 +97,7 @@ lazy val catsSettings = Seq( incOptions := incOptions.value.withLogRecompileOnMacro(false), libraryDependencies ++= Seq( "org.typelevel" %%% "machinist" % "0.6.8", - compilerPlugin("org.typelevel" %% "kind-projector" % "0.10.3") + compilerPlugin("org.typelevel" %% "kind-projector" % kindProjectorVersion) ) ++ macroDependencies(scalaVersion.value), ) ++ commonSettings ++ publishSettings ++ scoverageSettings ++ simulacrumSettings @@ -150,14 +164,6 @@ lazy val includeGeneratedSrc: Setting[_] = { } } -val scalatestVersion = "3.1.0-SNAP13" - -val scalatestplusScalaCheckVersion = "1.0.0-SNAP8" - -val scalaCheckVersion = "1.14.0" - -val disciplineVersion = "0.12.0-M3" - lazy val disciplineDependencies = Seq( libraryDependencies ++= Seq("org.scalacheck" %%% "scalacheck" % scalaCheckVersion, "org.typelevel" %%% "discipline-core" % disciplineVersion) @@ -632,8 +638,8 @@ lazy val binCompatTest = project .disablePlugins(CoursierPlugin) .settings(noPublishSettings) .settings( - crossScalaVersions := (crossScalaVersionsFromTravis in Global).value, - addCompilerPlugin("org.spire-math" %% "kind-projector" % "0.9.9"), + commonScalaVersionSettings, + addCompilerPlugin("org.typelevel" %% "kind-projector" % kindProjectorVersion), libraryDependencies ++= List( { if (priorTo2_13(scalaVersion.value)) diff --git a/core/src/main/scala-2.12-/cats/compat/lazyList.scala b/core/src/main/scala-2.12-/cats/compat/lazyList.scala new file mode 100644 index 0000000000..71b7a8131d --- /dev/null +++ b/core/src/main/scala-2.12-/cats/compat/lazyList.scala @@ -0,0 +1,9 @@ +package cats.compat + +object lazyList { + + def toLazyList[A](traversableOnce: TraversableOnce[A]): Stream[A] = traversableOnce.toStream + + def lazyListString: String = "Stream" + +} diff --git a/core/src/main/scala/cats/instances/stream.scala b/core/src/main/scala-2.12-/cats/instances/stream.scala similarity index 97% rename from core/src/main/scala/cats/instances/stream.scala rename to core/src/main/scala-2.12-/cats/instances/stream.scala index e8c38a50ac..83c4f9b18d 100644 --- a/core/src/main/scala/cats/instances/stream.scala +++ b/core/src/main/scala-2.12-/cats/instances/stream.scala @@ -5,7 +5,15 @@ import cats.syntax.show._ import scala.annotation.tailrec +/** + * For cross compile with backward compatibility + */ +trait LazyListInstances extends StreamInstances with StreamInstancesBinCompat0 { + val catsStdInstancesForLazyList = catsStdInstancesForStream +} + 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] { @@ -155,6 +163,7 @@ trait StreamInstances extends cats.kernel.instances.StreamInstances { new Show[Stream[A]] { def show(fa: Stream[A]): String = if (fa.isEmpty) "Stream()" else s"Stream(${fa.head.show}, ?)" } + } trait StreamInstancesBinCompat0 { diff --git a/core/src/main/scala-2.13+/cats/compat/lazyList.scala b/core/src/main/scala-2.13+/cats/compat/lazyList.scala new file mode 100644 index 0000000000..d9f95edff9 --- /dev/null +++ b/core/src/main/scala-2.13+/cats/compat/lazyList.scala @@ -0,0 +1,7 @@ +package cats.compat + +object lazyList { + def toLazyList[A](io: IterableOnce[A]): LazyList[A] = LazyList.from(io) + + def lazyListString: String = "LazyList" +} diff --git a/core/src/main/scala-2.13+/cats/instances/lazyList.scala b/core/src/main/scala-2.13+/cats/instances/lazyList.scala new file mode 100644 index 0000000000..a33fb71fba --- /dev/null +++ b/core/src/main/scala-2.13+/cats/instances/lazyList.scala @@ -0,0 +1,206 @@ +package cats +package instances +import cats.kernel +import cats.syntax.show._ + +import scala.annotation.tailrec + +//For cross compile with backward compatibility +trait StreamInstancesBinCompat0 + +//For cross compile with backward compatibility +trait StreamInstances extends LazyListInstances { + val catsStdInstancesForStream = catsStdInstancesForLazyList +} + +trait LazyListInstances extends cats.kernel.instances.StreamInstances { + 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: List[Iterator[Either[A, B]]] = Nil + var state: Either[A, Option[B]] = Left(a) + + @tailrec + def advance(): Unit = stack match { + case head :: tail => + if (head.hasNext) { + head.next match { + case Right(b) => + state = Right(Some(b)) + case Left(a) => + val nextFront = fn(a).iterator + stack = nextFront :: stack + advance() + } + } + else { + stack = tail + advance() + } + case Nil => + state = Right(None) + } + + @tailrec + def hasNext: Boolean = state match { + case Left(a) => + // this is the first run + stack = fn(a).iterator :: Nil + advance() + hasNext + case Right(o) => + o.isDefined + } + + @tailrec + def next(): B = state match { + case Left(a) => + // this is the first run + stack = fn(a).iterator :: Nil + advance() + next() + case Right(o) => + val b = o.get + advance() + b + } + } + + LazyList.from(it) + } + + 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.StreamMonoid[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 catsStdShowForLazyList[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}, ?)" + } + + implicit val catsStdTraverseFilterForLazyList: TraverseFilter[LazyList] = new TraverseFilter[LazyList] { + val traverse: Traverse[LazyList] = 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/data/OneAnd.scala b/core/src/main/scala/cats/data/OneAnd.scala index 466934a011..8cf8497436 100644 --- a/core/src/main/scala/cats/data/OneAnd.scala +++ b/core/src/main/scala/cats/data/OneAnd.scala @@ -4,6 +4,7 @@ package data import scala.annotation.tailrec import scala.collection.mutable.Builder import cats.instances.stream._ +import kernel.compat.lazyList._ /** * A data type which represents a single element (head) and some other @@ -191,22 +192,22 @@ sealed abstract private[data] class OneAndInstances extends OneAndLowPriority0 { } sealed abstract private[data] class OneAndLowPriority4 { - implicit val catsDataComonadForNonEmptyStream: Comonad[OneAnd[Stream, ?]] = - new Comonad[OneAnd[Stream, ?]] { - def coflatMap[A, B](fa: OneAnd[Stream, A])(f: OneAnd[Stream, A] => B): OneAnd[Stream, B] = { - @tailrec def consume(as: Stream[A], buf: Builder[B, Stream[B]]): Stream[B] = + implicit val catsDataComonadForNonEmptyStream: Comonad[OneAnd[LazyList, ?]] = + new Comonad[OneAnd[LazyList, ?]] { + def coflatMap[A, B](fa: OneAnd[LazyList, A])(f: OneAnd[LazyList, A] => B): OneAnd[LazyList, B] = { + @tailrec def consume(as: LazyList[A], buf: Builder[B, LazyList[B]]): LazyList[B] = if (as.isEmpty) buf.result else { val tail = as.tail consume(tail, buf += f(OneAnd(as.head, tail))) } - OneAnd(f(fa), consume(fa.tail, Stream.newBuilder)) + OneAnd(f(fa), consume(fa.tail, LazyList.newBuilder)) } - def extract[A](fa: OneAnd[Stream, A]): A = + def extract[A](fa: OneAnd[LazyList, A]): A = fa.head - def map[A, B](fa: OneAnd[Stream, A])(f: A => B): OneAnd[Stream, B] = + def map[A, B](fa: OneAnd[LazyList, A])(f: A => B): OneAnd[LazyList, B] = fa.map(f) } } diff --git a/core/src/main/scala/cats/data/ZipStream.scala b/core/src/main/scala/cats/data/ZipStream.scala index 775d870a58..5509318478 100644 --- a/core/src/main/scala/cats/data/ZipStream.scala +++ b/core/src/main/scala/cats/data/ZipStream.scala @@ -1,17 +1,18 @@ -package cats.data +package cats +package data -import cats.{Alternative, CommutativeApplicative, Eq} -import cats.instances.stream._ +import instances.stream._ +import kernel.compat.lazyList._ -class ZipStream[A](val value: Stream[A]) extends AnyVal +class ZipStream[A](val value: LazyList[A]) extends AnyVal object ZipStream { - def apply[A](value: Stream[A]): ZipStream[A] = new ZipStream(value) + def apply[A](value: LazyList[A]): ZipStream[A] = new ZipStream(value) implicit val catsDataAlternativeForZipStream: Alternative[ZipStream] with CommutativeApplicative[ZipStream] = new Alternative[ZipStream] with CommutativeApplicative[ZipStream] { - def pure[A](x: A): ZipStream[A] = new ZipStream(Stream.continually(x)) + def pure[A](x: A): ZipStream[A] = new ZipStream(LazyList.continually(x)) override def map[A, B](fa: ZipStream[A])(f: (A) => B): ZipStream[B] = ZipStream(fa.value.map(f)) @@ -22,10 +23,10 @@ object ZipStream { override def product[A, B](fa: ZipStream[A], fb: ZipStream[B]): ZipStream[(A, B)] = ZipStream(fa.value.zip(fb.value)) - def empty[A]: ZipStream[A] = ZipStream(Stream.empty[A]) + def empty[A]: ZipStream[A] = ZipStream(LazyList.empty[A]) def combineK[A](x: ZipStream[A], y: ZipStream[A]): ZipStream[A] = - ZipStream(Alternative[Stream].combineK(x.value, y.value)) + ZipStream(Alternative[LazyList].combineK(x.value, y.value)) } implicit def catsDataEqForZipStream[A: Eq]: Eq[ZipStream[A]] = Eq.by(_.value) diff --git a/core/src/main/scala/cats/data/package.scala b/core/src/main/scala/cats/data/package.scala index 8613e1b306..35751571d9 100644 --- a/core/src/main/scala/cats/data/package.scala +++ b/core/src/main/scala/cats/data/package.scala @@ -1,7 +1,9 @@ package cats - +import kernel.compat.lazyList._ +import compat.lazyList.toLazyList package object data { - type NonEmptyStream[A] = OneAnd[Stream, A] + + type NonEmptyStream[A] = OneAnd[LazyList, A] type ValidatedNel[+E, +A] = Validated[NonEmptyList[E], A] type IorNel[+B, +A] = Ior[NonEmptyList[B], A] type IorNec[+B, +A] = Ior[NonEmptyChain[B], A] @@ -11,10 +13,10 @@ package object data { type EitherNes[E, +A] = Either[NonEmptySet[E], A] type ValidatedNec[+E, +A] = Validated[NonEmptyChain[E], A] - def NonEmptyStream[A](head: A, tail: Stream[A] = Stream.empty): NonEmptyStream[A] = + def NonEmptyStream[A](head: A, tail: LazyList[A] = LazyList.empty): NonEmptyStream[A] = OneAnd(head, tail) def NonEmptyStream[A](head: A, tail: A*): NonEmptyStream[A] = - OneAnd(head, tail.toStream) + OneAnd(head, toLazyList(tail)) type NonEmptyMap[K, +A] = NonEmptyMapImpl.Type[K, A] val NonEmptyMap = NonEmptyMapImpl @@ -83,4 +85,7 @@ package object data { def apply[S, A](f: S => A, s: S): Store[S, A] = RepresentableStore[S => ?, S, A](f, s) } + + type ZipLazyList[A] = ZipStream[A] + val ZipLazyList = ZipStream } diff --git a/core/src/main/scala/cats/instances/package.scala b/core/src/main/scala/cats/instances/package.scala index c171490fbb..bd880a9cb1 100644 --- a/core/src/main/scala/cats/instances/package.scala +++ b/core/src/main/scala/cats/instances/package.scala @@ -41,6 +41,7 @@ package object instances { object sortedMap extends SortedMapInstances with SortedMapInstancesBinCompat0 with SortedMapInstancesBinCompat1 object sortedSet extends SortedSetInstances with SortedSetInstancesBinCompat0 object stream extends StreamInstances with StreamInstancesBinCompat0 + object lazyList extends LazyListInstances 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 5240b8c54b..845bf43bd2 100644 --- a/core/src/main/scala/cats/instances/parallel.scala +++ b/core/src/main/scala/cats/instances/parallel.scala @@ -1,9 +1,11 @@ -package cats.instances +package cats +package instances import cats.data._ import cats.kernel.Semigroup import cats.syntax.either._ import cats.{~>, Applicative, Apply, FlatMap, Functor, Monad, NonEmptyParallel, Parallel} +import kernel.compat.lazyList._ trait ParallelInstances extends ParallelInstances1 { implicit def catsParallelForEitherValidated[E: Semigroup]: Parallel[Either[E, ?], Validated[E, ?]] = @@ -64,17 +66,17 @@ trait ParallelInstances extends ParallelInstances1 { λ[Vector ~> ZipVector](v => new ZipVector(v)) } - implicit def catsStdParallelForZipStream[A]: Parallel[Stream, ZipStream] = - new Parallel[Stream, ZipStream] { + implicit def catsStdParallelForZipStream[A]: Parallel[LazyList, ZipStream] = + new Parallel[LazyList, ZipStream] { - def monad: Monad[Stream] = cats.instances.stream.catsStdInstancesForStream + def monad: Monad[LazyList] = cats.instances.stream.catsStdInstancesForStream def applicative: Applicative[ZipStream] = ZipStream.catsDataAlternativeForZipStream - def sequential: ZipStream ~> Stream = - λ[ZipStream ~> Stream](_.value) + def sequential: ZipStream ~> LazyList = + λ[ZipStream ~> LazyList](_.value) - def parallel: Stream ~> ZipStream = - λ[Stream ~> ZipStream](v => new ZipStream(v)) + def parallel: LazyList ~> ZipStream = + λ[LazyList ~> ZipStream](v => new ZipStream(v)) } implicit def catsParallelForEitherTNestedParallelValidated[F[_], M[_], E: Semigroup]( diff --git a/core/src/main/scala/cats/instances/sortedSet.scala b/core/src/main/scala/cats/instances/sortedSet.scala index c7c62cf455..24823a6ccc 100644 --- a/core/src/main/scala/cats/instances/sortedSet.scala +++ b/core/src/main/scala/cats/instances/sortedSet.scala @@ -5,6 +5,7 @@ import cats.kernel.{BoundedSemilattice, Hash, Order} import scala.collection.immutable.SortedSet import scala.annotation.tailrec import cats.implicits._ +import compat.lazyList._ trait SortedSetInstances extends SortedSetInstances1 { @@ -92,13 +93,13 @@ trait SortedSetInstancesBinCompat0 { class SortedSetOrder[A: Order] extends Order[SortedSet[A]] { def compare(a1: SortedSet[A], a2: SortedSet[A]): Int = Order[Int].compare(a1.size, a2.size) match { - case 0 => Order.compare(a1.toStream, a2.toStream) + case 0 => Order.compare(toLazyList(a1), toLazyList(a2)) case x => x } override def eqv(s1: SortedSet[A], s2: SortedSet[A]): Boolean = { implicit val x = Order[A].toOrdering - s1.toStream.corresponds(s2.toStream)(Order[A].eqv) + toLazyList(s1).corresponds(toLazyList(s2))(Order[A].eqv) } } @@ -125,7 +126,7 @@ class SortedSetHash[A: Order: Hash] extends Hash[SortedSet[A]] { } override def eqv(s1: SortedSet[A], s2: SortedSet[A]): Boolean = { implicit val x = Order[A].toOrdering - s1.toStream.corresponds(s2.toStream)(Order[A].eqv) + toLazyList(s1).corresponds(toLazyList(s2))(Order[A].eqv) } } diff --git a/kernel-laws/shared/src/test/scala/cats/kernel/laws/LawTests.scala b/kernel-laws/shared/src/test/scala/cats/kernel/laws/LawTests.scala index 92edc5f58e..156bdea44a 100644 --- a/kernel-laws/shared/src/test/scala/cats/kernel/laws/LawTests.scala +++ b/kernel-laws/shared/src/test/scala/cats/kernel/laws/LawTests.scala @@ -14,7 +14,7 @@ import org.scalatest.funsuite.AnyFunSuiteLike import scala.concurrent.duration.{Duration, FiniteDuration} import scala.collection.immutable.{BitSet, Queue} import scala.util.Random - +import compat.lazyList._ import java.util.UUID import java.util.concurrent.TimeUnit.{DAYS, HOURS, MICROSECONDS, MILLISECONDS, MINUTES, NANOSECONDS, SECONDS} @@ -131,7 +131,7 @@ class Tests extends AnyFunSuiteLike with Discipline { checkAll("Eq[List[HasEq[Int]]]", EqTests[List[HasEq[Int]]].eqv) checkAll("Eq[Option[HasEq[Int]]]", EqTests[Option[HasEq[Int]]].eqv) checkAll("Eq[Vector[HasEq[Int]]]", EqTests[Vector[HasEq[Int]]].eqv) - checkAll("Eq[Stream[HasEq[Int]]]", EqTests[Stream[HasEq[Int]]].eqv) + checkAll("Eq[Stream[HasEq[Int]]]", EqTests[LazyList[HasEq[Int]]].eqv) checkAll("Eq[Queue[HasEq[Int]]]", EqTests[Queue[HasEq[Int]]].eqv) checkAll("PartialOrder[Set[Int]]", PartialOrderTests[Set[Int]].partialOrder) @@ -144,7 +144,7 @@ class Tests extends AnyFunSuiteLike with Discipline { checkAll("PartialOrder[Option[HasPartialOrder[Int]]]", PartialOrderTests[Option[HasPartialOrder[Int]]].partialOrder) checkAll("PartialOrder[List[HasPartialOrder[Int]]]", PartialOrderTests[List[HasPartialOrder[Int]]].partialOrder) checkAll("PartialOrder[Vector[HasPartialOrder[Int]]]", PartialOrderTests[Vector[HasPartialOrder[Int]]].partialOrder) - checkAll("PartialOrder[Stream[HasPartialOrder[Int]]]", PartialOrderTests[Stream[HasPartialOrder[Int]]].partialOrder) + checkAll("PartialOrder[Stream[HasPartialOrder[Int]]]", PartialOrderTests[LazyList[HasPartialOrder[Int]]].partialOrder) checkAll("PartialOrder[Queue[HasPartialOrder[Int]]]", PartialOrderTests[Queue[HasPartialOrder[Int]]].partialOrder) checkAll("Semilattice.asMeetPartialOrder[Set[Int]]", PartialOrderTests(Semilattice.asMeetPartialOrder[Set[Int]]).partialOrder) @@ -169,7 +169,7 @@ class Tests extends AnyFunSuiteLike with Discipline { checkAll("Order[Option[String]]", OrderTests[Option[String]].order) checkAll("Order[List[String]", OrderTests[List[String]].order) checkAll("Order[Vector[Int]]", OrderTests[Vector[Int]].order) - checkAll("Order[Stream[Int]]", OrderTests[Stream[Int]].order) + checkAll("Order[Stream[Int]]", OrderTests[LazyList[Int]].order) checkAll("Order[Queue[Int]]", OrderTests[Queue[Int]].order) checkAll("fromOrdering[Int]", OrderTests(Order.fromOrdering[Int]).order) checkAll("Order.reverse(Order[Int])", OrderTests(Order.reverse(Order[Int])).order) @@ -186,8 +186,8 @@ class Tests extends AnyFunSuiteLike with Discipline { checkAll("Monoid[List[Int]]", SerializableTests.serializable(Monoid[List[Int]])) checkAll("Monoid[Vector[Int]]", MonoidTests[Vector[Int]].monoid) checkAll("Monoid[Vector[Int]]", SerializableTests.serializable(Monoid[Vector[Int]])) - checkAll("Monoid[Stream[Int]]", MonoidTests[Stream[Int]].monoid) - checkAll("Monoid[Stream[Int]]", SerializableTests.serializable(Monoid[Stream[Int]])) + checkAll("Monoid[Stream[Int]]", MonoidTests[LazyList[Int]].monoid) + checkAll("Monoid[Stream[Int]]", SerializableTests.serializable(Monoid[LazyList[Int]])) checkAll("Monoid[List[String]]", MonoidTests[List[String]].monoid) checkAll("Monoid[List[String]]", SerializableTests.serializable(Monoid[List[String]])) checkAll("Monoid[Map[String, String]]", MonoidTests[Map[String, String]].monoid) @@ -245,7 +245,7 @@ class Tests extends AnyFunSuiteLike with Discipline { checkAll("Hash[Option[String]]", HashTests[Option[String]].hash) checkAll("Hash[List[String]]", HashTests[List[String]].hash) checkAll("Hash[Vector[Int]]", HashTests[Vector[Int]].hash) - checkAll("Hash[Stream[Int]]", HashTests[Stream[Int]].hash) + checkAll("Hash[Stream[Int]]", HashTests[LazyList[Int]].hash) checkAll("Hash[Set[Int]]", HashTests[Set[Int]].hash) checkAll("Hash[(Int, String)]", HashTests[(Int, String)].hash) checkAll("Hash[Either[Int, String]]", HashTests[Either[Int, String]].hash) diff --git a/kernel/src/main/scala-2.12-/cats/kernel/compat/lazyList.scala b/kernel/src/main/scala-2.12-/cats/kernel/compat/lazyList.scala new file mode 100644 index 0000000000..59c263f43b --- /dev/null +++ b/kernel/src/main/scala-2.12-/cats/kernel/compat/lazyList.scala @@ -0,0 +1,6 @@ +package cats.kernel.compat + +object lazyList { + type LazyList[+A] = Stream[A] + val LazyList = Stream +} diff --git a/kernel/src/main/scala-2.13+/cats/kernel/compat/lazyList.scala b/kernel/src/main/scala-2.13+/cats/kernel/compat/lazyList.scala new file mode 100644 index 0000000000..5b8fcead5f --- /dev/null +++ b/kernel/src/main/scala-2.13+/cats/kernel/compat/lazyList.scala @@ -0,0 +1,9 @@ +package cats +package kernel + +package compat + +object lazyList { + type LazyList[+A] = scala.LazyList[A] //this is needed only to avoid unused import warnings on Scala 2.13 + val LazyList = scala.LazyList +} diff --git a/kernel/src/main/scala/cats/kernel/instances/StreamInstances.scala b/kernel/src/main/scala/cats/kernel/instances/StreamInstances.scala index 3aff14e137..ff21b0ffbd 100644 --- a/kernel/src/main/scala/cats/kernel/instances/StreamInstances.scala +++ b/kernel/src/main/scala/cats/kernel/instances/StreamInstances.scala @@ -1,55 +1,55 @@ package cats.kernel package instances +import compat.lazyList._ trait StreamInstances extends StreamInstances1 { - implicit def catsKernelStdOrderForStream[A: Order]: Order[Stream[A]] = + implicit def catsKernelStdOrderForStream[A: Order]: Order[LazyList[A]] = new StreamOrder[A] - implicit def catsKernelStdMonoidForStream[A]: Monoid[Stream[A]] = + implicit def catsKernelStdMonoidForStream[A]: Monoid[LazyList[A]] = new StreamMonoid[A] } trait StreamInstances1 extends StreamInstances2 { - implicit def catsKernelStdPartialOrderForStream[A: PartialOrder]: PartialOrder[Stream[A]] = + implicit def catsKernelStdPartialOrderForStream[A: PartialOrder]: PartialOrder[LazyList[A]] = new StreamPartialOrder[A] - implicit def catsKernelStdHashForStream[A: Hash]: Hash[Stream[A]] = + implicit def catsKernelStdHashForStream[A: Hash]: Hash[LazyList[A]] = new StreamHash[A] } trait StreamInstances2 { - implicit def catsKernelStdEqForStream[A: Eq]: Eq[Stream[A]] = + implicit def catsKernelStdEqForStream[A: Eq]: Eq[LazyList[A]] = new StreamEq[A] } -class StreamOrder[A](implicit ev: Order[A]) extends Order[Stream[A]] { - def compare(xs: Stream[A], ys: Stream[A]): Int = +class StreamOrder[A](implicit ev: Order[A]) extends Order[LazyList[A]] { + def compare(xs: LazyList[A], ys: LazyList[A]): Int = if (xs eq ys) 0 else StaticMethods.iteratorCompare(xs.iterator, ys.iterator) } -class StreamPartialOrder[A](implicit ev: PartialOrder[A]) extends PartialOrder[Stream[A]] { - def partialCompare(xs: Stream[A], ys: Stream[A]): Double = +class StreamPartialOrder[A](implicit ev: PartialOrder[A]) extends PartialOrder[LazyList[A]] { + def partialCompare(xs: LazyList[A], ys: LazyList[A]): Double = if (xs eq ys) 0.0 else StaticMethods.iteratorPartialCompare(xs.iterator, ys.iterator) } -class StreamHash[A](implicit ev: Hash[A]) extends StreamEq[A]()(ev) with Hash[Stream[A]] { - def hash(xs: Stream[A]): Int = StaticMethods.orderedHash(xs) +class StreamHash[A](implicit ev: Hash[A]) extends StreamEq[A]()(ev) with Hash[LazyList[A]] { + def hash(xs: LazyList[A]): Int = StaticMethods.orderedHash(xs) } -class StreamEq[A](implicit ev: Eq[A]) extends Eq[Stream[A]] { - def eqv(xs: Stream[A], ys: Stream[A]): Boolean = +class StreamEq[A](implicit ev: Eq[A]) extends Eq[LazyList[A]] { + def eqv(xs: LazyList[A], ys: LazyList[A]): Boolean = if (xs eq ys) true else StaticMethods.iteratorEq(xs.iterator, ys.iterator) } -class StreamMonoid[A] extends Monoid[Stream[A]] { - def empty: Stream[A] = Stream.empty - def combine(x: Stream[A], y: Stream[A]): Stream[A] = x ++ y +class StreamMonoid[A] extends Monoid[LazyList[A]] { + def empty: LazyList[A] = LazyList.empty + def combine(x: LazyList[A], y: LazyList[A]): LazyList[A] = x ++ y + override def combineN(x: LazyList[A], n: Int): LazyList[A] = + StaticMethods.combineNIterable(LazyList.newBuilder[A], x, n) - override def combineN(x: Stream[A], n: Int): Stream[A] = - StaticMethods.combineNIterable(Stream.newBuilder[A], x, n) - - override def combineAll(xs: TraversableOnce[Stream[A]]): Stream[A] = - StaticMethods.combineAllIterable(Stream.newBuilder[A], xs) + override def combineAll(xs: TraversableOnce[LazyList[A]]): LazyList[A] = + StaticMethods.combineAllIterable(LazyList.newBuilder[A], xs) } diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index a1025c9219..c2fd56ec3c 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -1,7 +1,7 @@ package cats package laws package discipline - +import kernel.compat.lazyList._ import cats.data.NonEmptyList.ZipNonEmptyList import cats.data.NonEmptyVector.ZipNonEmptyVector import scala.util.{Failure, Success, Try} @@ -68,7 +68,7 @@ object arbitrary extends ArbitraryInstances0 { Arbitrary(implicitly[Arbitrary[List[A]]].arbitrary.map(v => new ZipList(v))) implicit def catsLawsArbitraryForZipStream[A](implicit A: Arbitrary[A]): Arbitrary[ZipStream[A]] = - Arbitrary(implicitly[Arbitrary[Stream[A]]].arbitrary.map(v => new ZipStream(v))) + Arbitrary(implicitly[Arbitrary[LazyList[A]]].arbitrary.map(v => new ZipStream(v))) implicit def catsLawsArbitraryForZipNonEmptyVector[A](implicit A: Arbitrary[A]): Arbitrary[ZipNonEmptyVector[A]] = Arbitrary(implicitly[Arbitrary[NonEmptyVector[A]]].arbitrary.map(nev => new ZipNonEmptyVector(nev))) diff --git a/tests/src/test/scala/cats/tests/BinCodecInvariantMonoidalSuite.scala b/tests/src/test/scala/cats/tests/BinCodecInvariantMonoidalSuite.scala index 768668d213..a66c8c3c98 100644 --- a/tests/src/test/scala/cats/tests/BinCodecInvariantMonoidalSuite.scala +++ b/tests/src/test/scala/cats/tests/BinCodecInvariantMonoidalSuite.scala @@ -8,6 +8,7 @@ import cats.implicits._ import cats.Eq import cats.kernel.laws.discipline.{MonoidTests, SemigroupTests} import org.scalacheck.{Arbitrary, Gen} +import kernel.compat.lazyList._ object BinCodecInvariantMonoidalSuite { final case class MiniList[+A] private (val toList: List[A]) extends AnyVal { @@ -122,7 +123,7 @@ object BinCodecInvariantMonoidalSuite { bitCount <- Gen.oneOf(1, 2, 3) shuffleSeed <- Gen.choose(Long.MinValue, Long.MaxValue) } yield { - val binValues: Stream[Bin] = Stream(false, true).replicateA(bitCount).map(MiniList.unsafe(_)) + val binValues: LazyList[Bin] = LazyList(false, true).replicateA(bitCount).map(MiniList.unsafe(_)) val pairs: List[(A, Bin)] = new scala.util.Random(seed = shuffleSeed).shuffle(exA.allValues).toList.zip(binValues) val aToBin: Map[A, Bin] = pairs.toMap val binToA: Map[Bin, A] = pairs.map(_.swap).toMap diff --git a/tests/src/test/scala/cats/tests/FoldableSuite.scala b/tests/src/test/scala/cats/tests/FoldableSuite.scala index af4dc6c704..9eeb216727 100644 --- a/tests/src/test/scala/cats/tests/FoldableSuite.scala +++ b/tests/src/test/scala/cats/tests/FoldableSuite.scala @@ -3,10 +3,12 @@ package tests import org.scalacheck.Arbitrary import scala.util.Try -import scala.collection.immutable._ +import scala.collection.immutable.{SortedMap, SortedSet} import cats.instances.all._ import cats.data._ import cats.laws.discipline.arbitrary._ +import kernel.compat.lazyList._ +import compat.lazyList.toLazyList abstract class FoldableSuite[F[_]: Foldable](name: String)(implicit ArbFInt: Arbitrary[F[Int]], ArbFString: Arbitrary[F[String]]) @@ -335,8 +337,8 @@ class FoldableSuiteAdditional extends CatsSuite { checkMonadicFoldsStackSafety[List](_.toList) } - test("Foldable[Stream].foldM stack safety") { - checkMonadicFoldsStackSafety[Stream](_.toStream) + test("Foldable[LazyList].foldM stack safety") { + checkMonadicFoldsStackSafety[LazyList](toLazyList) } test("Foldable[Vector].foldM/existsM/forallM/findM/collectFirstSomeM stack safety") { @@ -365,47 +367,51 @@ class FoldableSuiteAdditional extends CatsSuite { checkMonadicFoldsStackSafety[NonEmptyStream](xs => NonEmptyStream(xs.head, xs.tail: _*)) } - test("Foldable[Stream]") { - val F = Foldable[Stream] - - def bomb[A]: A = sys.error("boom") - val dangerous = 0 #:: 1 #:: 2 #:: bomb[Stream[Int]] + val F = Foldable[LazyList] + def bomb[A]: A = sys.error("boom") + val dangerous = 0 #:: 1 #:: 2 #:: bomb[Int] #:: LazyList.empty + def boom[A]: LazyList[A] = + bomb[A] #:: LazyList.empty + test("Foldable[LazyList] doesn't blow up") { // doesn't blow up - this also ensures it works for infinite streams. assert(contains(dangerous, 2).value) + } - // lazy results don't blow up unless you call .value on them. - val doom: Eval[Boolean] = contains(dangerous, -1) + test("Foldable[LazyList] lazy results don't blow up unless you call .value on them") { + contains(dangerous, -1) + } - // ensure that the Lazy[B] param to foldRight is actually being - // handled lazily. it only needs to be evaluated if we reach the + test("Foldable[LazyList] param to foldRight is actually being handled lazily") { + // ensure that the . it only needs to be evaluated if we reach the // "end" of the fold. val trap = Eval.later(bomb[Boolean]) - val result = F.foldRight(1 #:: 2 #:: Stream.empty, trap) { (n, lb) => + val result = F.foldRight(1 #:: 2 #:: LazyList.empty, trap) { (n, lb) => if (n == 2) Now(true) else lb } assert(result.value) + } - // test trampolining - val large = Stream((1 to 10000): _*) + test("Foldable[LazyList] trampolining") { + val large = LazyList((1 to 10000): _*) assert(contains(large, 10000).value) + } - // test laziness of foldM + test("Foldable[LazyList] laziness of foldM") { dangerous.foldM(0)((acc, a) => if (a < 2) Some(acc + a) else None) should ===(None) - } - 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) + def foldableLazyListWithDefaultImpl = new Foldable[LazyList] { + def foldLeft[A, B](fa: LazyList[A], b: B)(f: (B, A) => B): B = + instances.lazyList.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) + def foldRight[A, B](fa: LazyList[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = + instances.lazyList.catsStdInstancesForLazyList.foldRight(fa, lb)(f) } test(".foldLeftM short-circuiting") { - implicit val F = foldableStreamWithDefaultImpl - val ns = Stream.continually(1) + implicit val F = foldableLazyListWithDefaultImpl + val ns = LazyList.continually(1) val res = F.foldLeftM[Either[Int, ?], Int, Int](ns, 0) { (sum, n) => if (sum >= 100000) Left(sum) else Right(sum + n) } @@ -413,33 +419,30 @@ class FoldableSuiteAdditional extends CatsSuite { } test(".foldLeftM short-circuiting optimality") { - implicit val F = foldableStreamWithDefaultImpl + implicit val F = foldableLazyListWithDefaultImpl // test that no more elements are evaluated than absolutely necessary - def concatUntil(ss: Stream[String], stop: String): Either[String, String] = + def concatUntil(ss: LazyList[String], stop: String): Either[String, String] = F.foldLeftM[Either[String, ?], String, String](ss, "") { (acc, s) => if (s == stop) Left(acc) else Right(acc + s) } - def boom: Stream[String] = sys.error("boom") - assert(concatUntil("STOP" #:: boom, "STOP") == Left("")) - assert(concatUntil("Zero" #:: "STOP" #:: boom, "STOP") == Left("Zero")) - assert(concatUntil("Zero" #:: "One" #:: "STOP" #:: boom, "STOP") == Left("ZeroOne")) + assert(concatUntil("STOP" #:: boom[String], "STOP") == Left("")) + assert(concatUntil("Zero" #:: "STOP" #:: boom[String], "STOP") == Left("Zero")) + assert(concatUntil("Zero" #:: "One" #:: "STOP" #:: boom[String], "STOP") == Left("ZeroOne")) } test(".existsM/.forallM short-circuiting") { - implicit val F = foldableStreamWithDefaultImpl - def boom: Stream[Boolean] = sys.error("boom") - assert(F.existsM[Id, Boolean](true #:: boom)(identity) == true) - assert(F.forallM[Id, Boolean](false #:: boom)(identity) == false) + implicit val F = foldableLazyListWithDefaultImpl + assert(F.existsM[Id, Boolean](true #:: boom[Boolean])(identity) == true) + assert(F.forallM[Id, Boolean](false #:: boom[Boolean])(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)) + implicit val F = foldableLazyListWithDefaultImpl + assert((1 #:: boom[Int]).findM[Id](_ > 0) == Some(1)) + assert((1 #:: boom[Int]).collectFirstSomeM[Id, Int](Option.apply) == Some(1)) } test("Foldable[List] doesn't break substitution") { @@ -461,8 +464,8 @@ class FoldableSortedSetSuite extends FoldableSuite[SortedSet]("sortedSet") { def iterator[T](set: SortedSet[T]): Iterator[T] = set.iterator } -class FoldableStreamSuite extends FoldableSuite[Stream]("stream") { - def iterator[T](stream: Stream[T]): Iterator[T] = stream.iterator +class FoldableLazyListSuite extends FoldableSuite[LazyList]("lazyList") { + def iterator[T](list: LazyList[T]): Iterator[T] = list.iterator } class FoldableSortedMapSuite extends FoldableSuite[SortedMap[Int, ?]]("sortedMap") { diff --git a/tests/src/test/scala/cats/tests/LazyListSuite.scala b/tests/src/test/scala/cats/tests/LazyListSuite.scala new file mode 100644 index 0000000000..f07dbdf1e6 --- /dev/null +++ b/tests/src/test/scala/cats/tests/LazyListSuite.scala @@ -0,0 +1,63 @@ +package cats +package tests + +import cats.laws.discipline.{ + AlternativeTests, + CoflatMapTests, + CommutativeApplyTests, + MonadTests, + SemigroupalTests, + SerializableTests, + TraverseFilterTests, + TraverseTests +} +import cats.data.ZipStream +import cats.laws.discipline.arbitrary._ +import kernel.compat.lazyList._ +import compat.lazyList.lazyListString + +class LazyListSuite extends CatsSuite { + checkAll("LazyList[Int]", SemigroupalTests[LazyList].semigroupal[Int, Int, Int]) + checkAll("Semigroupal[LazyList]", SerializableTests.serializable(Semigroupal[LazyList])) + + checkAll("LazyList[Int]", CoflatMapTests[LazyList].coflatMap[Int, Int, Int]) + checkAll("CoflatMap[LazyList]", SerializableTests.serializable(CoflatMap[LazyList])) + + checkAll("LazyList[Int]", AlternativeTests[LazyList].alternative[Int, Int, Int]) + checkAll("Alternative[LazyList]", SerializableTests.serializable(Alternative[LazyList])) + + checkAll("LazyList[Int]", MonadTests[LazyList].monad[Int, Int, Int]) + checkAll("Monad[LazyList]", SerializableTests.serializable(Monad[LazyList])) + + checkAll("LazyList[Int] with Option", TraverseTests[LazyList].traverse[Int, Int, Int, Set[Int], Option, Option]) + checkAll("Traverse[LazyList]", SerializableTests.serializable(Traverse[LazyList])) + + checkAll("LazyList[Int]", TraverseFilterTests[LazyList].traverseFilter[Int, Int, Int]) + checkAll("TraverseFilter[LazyList]", SerializableTests.serializable(TraverseFilter[LazyList])) + + // Can't test applicative laws as they don't terminate + checkAll("ZipStream[Int]", CommutativeApplyTests[ZipStream].apply[Int, Int, Int]) + + test("show") { + LazyList(1, 2, 3).show should ===(s"$lazyListString(1, ?)") + LazyList.empty[Int].show should ===(s"$lazyListString()") + } + + test("Show[Stream] is referentially transparent, unlike Stream.toString") { + forAll { lazyList: LazyList[Int] => + if (!lazyList.isEmpty) { + val unevaluatedLL = lazyList.map(identity) + val initialShow = unevaluatedLL.show + + // Evaluating the tail can cause Stream.toString to return different values, + // depending on the internal state of the Stream. Show[Stream] should return + // consistent values independent of internal state. + unevaluatedLL.tail + initialShow should ===(unevaluatedLL.show) + } else { + lazyList.show should ===(lazyList.toString) + } + } + } + +} diff --git a/tests/src/test/scala/cats/tests/OneAndSuite.scala b/tests/src/test/scala/cats/tests/OneAndSuite.scala index f73f0e20f7..d5bccdfe19 100644 --- a/tests/src/test/scala/cats/tests/OneAndSuite.scala +++ b/tests/src/test/scala/cats/tests/OneAndSuite.scala @@ -19,17 +19,18 @@ import cats.laws.discipline.{ TraverseTests } import cats.laws.discipline.arbitrary._ - +import kernel.compat.lazyList._ +import compat.lazyList.toLazyList class OneAndSuite extends CatsSuite { // Lots of collections here.. telling ScalaCheck to calm down a bit implicit override val generatorDrivenConfig: PropertyCheckConfiguration = PropertyCheckConfiguration(minSuccessful = 20, sizeRange = 5) - checkAll("OneAnd[Stream, Int]", EqTests[OneAnd[Stream, Int]].eqv) + checkAll("OneAnd[Stream, Int]", EqTests[OneAnd[LazyList, Int]].eqv) checkAll("OneAnd[Stream, Int] with Option", - NonEmptyTraverseTests[OneAnd[Stream, ?]].nonEmptyTraverse[Option, Int, Int, Int, Int, Option, Option]) - checkAll("NonEmptyTraverse[OneAnd[Stream, A]]", SerializableTests.serializable(NonEmptyTraverse[OneAnd[Stream, ?]])) + NonEmptyTraverseTests[OneAnd[LazyList, ?]].nonEmptyTraverse[Option, Int, Int, Int, Int, Option, Option]) + checkAll("NonEmptyTraverse[OneAnd[Stream, A]]", SerializableTests.serializable(NonEmptyTraverse[OneAnd[LazyList, ?]])) { implicit val traverse = OneAnd.catsDataTraverseForOneAnd(ListWrapper.traverse) @@ -38,8 +39,8 @@ class OneAndSuite extends CatsSuite { checkAll("Traverse[OneAnd[ListWrapper, A]]", SerializableTests.serializable(Traverse[OneAnd[ListWrapper, ?]])) } - checkAll("OneAnd[Stream, Int]", ReducibleTests[OneAnd[Stream, ?]].reducible[Option, Int, Int]) - checkAll("Reducible[OneAnd[Stream, ?]]", SerializableTests.serializable(Reducible[OneAnd[Stream, ?]])) + checkAll("OneAnd[Stream, Int]", ReducibleTests[OneAnd[LazyList, ?]].reducible[Option, Int, Int]) + checkAll("Reducible[OneAnd[Stream, ?]]", SerializableTests.serializable(Reducible[OneAnd[LazyList, ?]])) implicit val iso = SemigroupalTests.Isomorphisms .invariant[OneAnd[ListWrapper, ?]](OneAnd.catsDataFunctorForOneAnd(ListWrapper.functor)) @@ -67,9 +68,9 @@ class OneAndSuite extends CatsSuite { { implicit val alternative = ListWrapper.alternative checkAll("OneAnd[ListWrapper, Int]", SemigroupKTests[OneAnd[ListWrapper, ?]].semigroupK[Int]) - checkAll("OneAnd[Stream, Int]", SemigroupTests[OneAnd[Stream, Int]].semigroup) + checkAll("OneAnd[Stream, Int]", SemigroupTests[OneAnd[LazyList, Int]].semigroup) checkAll("SemigroupK[OneAnd[ListWrapper, A]]", SerializableTests.serializable(SemigroupK[OneAnd[ListWrapper, ?]])) - checkAll("Semigroup[NonEmptyStream[Int]]", SerializableTests.serializable(Semigroup[OneAnd[Stream, Int]])) + checkAll("Semigroup[NonEmptyStream[Int]]", SerializableTests.serializable(Semigroup[OneAnd[LazyList, Int]])) } { @@ -85,9 +86,10 @@ class OneAndSuite extends CatsSuite { implicitly[Comonad[NonEmptyStream]] } - implicit val iso2 = SemigroupalTests.Isomorphisms.invariant[OneAnd[Stream, ?]] + implicit val iso2 = SemigroupalTests.Isomorphisms.invariant[OneAnd[LazyList, ?]] - checkAll("NonEmptyStream[Int]", MonadTests[NonEmptyStream].monad[Int, Int, Int]) + //OneAnd's tailRecM fails on LazyList due to the fact that. todo: replace NonEmptyStream with NonEmptyLazyList using newtype https://github.com/typelevel/cats/issues/2903 + checkAll("NonEmptyStream[Int]", MonadTests[NonEmptyStream].stackUnsafeMonad[Int, Int, Int]) checkAll("Monad[NonEmptyStream[A]]", SerializableTests.serializable(Monad[NonEmptyStream])) checkAll("NonEmptyStream[Int]", ComonadTests[NonEmptyStream].comonad[Int, Int, Int]) @@ -110,11 +112,11 @@ class OneAndSuite extends CatsSuite { test("Show is formatted correctly") { val oneAnd = NonEmptyStream("Test") - oneAnd.show should ===("OneAnd(Test, Stream())") + oneAnd.show should ===(s"OneAnd(Test, ${compat.lazyList.lazyListString}())") } test("Creating OneAnd + unwrap is identity") { - forAll { (i: Int, tail: Stream[Int]) => + forAll { (i: Int, tail: LazyList[Int]) => val stream = i #:: tail val oneAnd = NonEmptyStream(i, tail: _*) stream should ===(oneAnd.unwrap) @@ -224,6 +226,6 @@ class ReducibleNonEmptyStreamSuite extends ReducibleSuite[NonEmptyStream]("NonEm // if we inline this we get a bewildering implicit numeric widening // error message in Scala 2.10 val tailStart: Long = start + 1L - NonEmptyStream(start, (tailStart).to(endInclusive).toStream) + NonEmptyStream(start, toLazyList(tailStart.to(endInclusive))) } } diff --git a/tests/src/test/scala/cats/tests/ParallelSuite.scala b/tests/src/test/scala/cats/tests/ParallelSuite.scala index dabb16cfb9..d8c0b4194f 100644 --- a/tests/src/test/scala/cats/tests/ParallelSuite.scala +++ b/tests/src/test/scala/cats/tests/ParallelSuite.scala @@ -11,6 +11,7 @@ import cats.laws.discipline.eq._ import cats.laws.discipline.arbitrary._ import org.typelevel.discipline.scalatest.Discipline import scala.collection.immutable.SortedSet +import kernel.compat.lazyList._ class ParallelSuite extends CatsSuite with ApplicativeErrorForEitherTest { @@ -302,7 +303,7 @@ class ParallelSuite extends CatsSuite with ApplicativeErrorForEitherTest { } test("ParMap over Stream should be consistent with zip") { - forAll { (as: Stream[Int], bs: Stream[Int], cs: Stream[Int]) => + forAll { (as: LazyList[Int], bs: LazyList[Int], cs: LazyList[Int]) => val zipped = as .zip(bs) .map { @@ -342,7 +343,7 @@ class ParallelSuite extends CatsSuite with ApplicativeErrorForEitherTest { } test("ParTupled of Stream should be consistent with ParMap of Tuple.apply") { - forAll { (fa: Stream[Int], fb: Stream[Int], fc: Stream[Int], fd: Stream[Int]) => + forAll { (fa: LazyList[Int], fb: LazyList[Int], fc: LazyList[Int], fd: LazyList[Int]) => (fa, fb, fc, fd).parTupled should ===((fa, fb, fc, fd).parMapN(Tuple4.apply)) } } @@ -360,7 +361,7 @@ class ParallelSuite extends CatsSuite with ApplicativeErrorForEitherTest { } test("ParTupled of Stream should be consistent with zip") { - forAll { (fa: Stream[Int], fb: Stream[Int], fc: Stream[Int], fd: Stream[Int]) => + forAll { (fa: LazyList[Int], fb: LazyList[Int], fc: LazyList[Int], fd: LazyList[Int]) => (fa, fb, fc, fd).parTupled should ===(fa.zip(fb).zip(fc).zip(fd).map { case (((a, b), c), d) => (a, b, c, d) }) } } @@ -442,7 +443,7 @@ class ParallelSuite extends CatsSuite with ApplicativeErrorForEitherTest { NonEmptyParallelTests[Vector, ZipVector].nonEmptyParallel[Int, String]) checkAll("NonEmptyParallel[List, ZipList]", NonEmptyParallelTests[List, ZipList].nonEmptyParallel[Int, String]) // Can't test Parallel here, as Applicative[ZipStream].pure doesn't terminate - checkAll("Parallel[Stream, ZipStream]", NonEmptyParallelTests[Stream, ZipStream].nonEmptyParallel[Int, String]) + checkAll("Parallel[Stream, ZipStream]", NonEmptyParallelTests[LazyList, ZipStream].nonEmptyParallel[Int, String]) checkAll("NonEmptyParallel[NonEmptyVector, ZipNonEmptyVector]", NonEmptyParallelTests[NonEmptyVector, ZipNonEmptyVector].nonEmptyParallel[Int, String]) checkAll("NonEmptyParallel[NonEmptyList, ZipNonEmptyList]", diff --git a/tests/src/test/scala/cats/tests/RegressionSuite.scala b/tests/src/test/scala/cats/tests/RegressionSuite.scala index a04d5f08c4..5f55a68e60 100644 --- a/tests/src/test/scala/cats/tests/RegressionSuite.scala +++ b/tests/src/test/scala/cats/tests/RegressionSuite.scala @@ -4,6 +4,8 @@ package tests import cats.data.{Const, NonEmptyList, StateT} import scala.collection.mutable import scala.collection.immutable.SortedMap +import kernel.compat.lazyList._ + class RegressionSuite extends CatsSuite { // toy state class @@ -112,7 +114,7 @@ class RegressionSuite extends CatsSuite { // shouldn't have ever evaluted validate(8) checkAndResetCount(3) - Stream(1, 2, 6, 8).traverse(validate) should ===(Either.left("6 is greater than 5")) + LazyList(1, 2, 6, 8).traverse(validate) should ===(Either.left("6 is greater than 5")) checkAndResetCount(3) type StringMap[A] = SortedMap[String, A] @@ -132,7 +134,7 @@ class RegressionSuite extends CatsSuite { List(1, 2, 6, 8).traverse_(validate) should ===(Either.left("6 is greater than 5")) checkAndResetCount(3) - Stream(1, 2, 6, 8).traverse_(validate) should ===(Either.left("6 is greater than 5")) + LazyList(1, 2, 6, 8).traverse_(validate) should ===(Either.left("6 is greater than 5")) checkAndResetCount(3) Vector(1, 2, 6, 8).traverse_(validate) should ===(Either.left("6 is greater than 5")) diff --git a/tests/src/test/scala/cats/tests/StreamSuite.scala b/tests/src/test/scala/cats/tests/StreamSuite.scala deleted file mode 100644 index b26c744464..0000000000 --- a/tests/src/test/scala/cats/tests/StreamSuite.scala +++ /dev/null @@ -1,61 +0,0 @@ -package cats -package tests - -import cats.laws.discipline.{ - AlternativeTests, - CoflatMapTests, - CommutativeApplyTests, - MonadTests, - SemigroupalTests, - SerializableTests, - TraverseFilterTests, - TraverseTests -} -import cats.data.ZipStream -import cats.laws.discipline.arbitrary._ - -class StreamSuite extends CatsSuite { - checkAll("Stream[Int]", SemigroupalTests[Stream].semigroupal[Int, Int, Int]) - checkAll("Semigroupal[Stream]", SerializableTests.serializable(Semigroupal[Stream])) - - checkAll("Stream[Int]", CoflatMapTests[Stream].coflatMap[Int, Int, Int]) - checkAll("CoflatMap[Stream]", SerializableTests.serializable(CoflatMap[Stream])) - - checkAll("Stream[Int]", AlternativeTests[Stream].alternative[Int, Int, Int]) - checkAll("Alternative[Stream]", SerializableTests.serializable(Alternative[Stream])) - - checkAll("Stream[Int]", MonadTests[Stream].monad[Int, Int, Int]) - checkAll("Monad[Stream]", SerializableTests.serializable(Monad[Stream])) - - checkAll("Stream[Int] with Option", TraverseTests[Stream].traverse[Int, Int, Int, Set[Int], Option, Option]) - checkAll("Traverse[Stream]", SerializableTests.serializable(Traverse[Stream])) - - checkAll("Stream[Int]", TraverseFilterTests[Stream].traverseFilter[Int, Int, Int]) - checkAll("TraverseFilter[Stream]", SerializableTests.serializable(TraverseFilter[Stream])) - - // Can't test applicative laws as they don't terminate - checkAll("ZipStream[Int]", CommutativeApplyTests[ZipStream].apply[Int, Int, Int]) - - test("show") { - Stream(1, 2, 3).show should ===("Stream(1, ?)") - Stream.empty[Int].show should ===("Stream()") - } - - test("Show[Stream] is referentially transparent, unlike Stream.toString") { - forAll { stream: Stream[Int] => - if (!stream.isEmpty) { - val unevaluatedStream = stream.map(identity) - val initialShow = unevaluatedStream.show - - // Evaluating the tail can cause Stream.toString to return different values, - // depending on the internal state of the Stream. Show[Stream] should return - // consistent values independent of internal state. - unevaluatedStream.tail - initialShow should ===(unevaluatedStream.show) - } else { - stream.show should ===(stream.toString) - } - } - } - -} diff --git a/tests/src/test/scala/cats/tests/TraverseSuite.scala b/tests/src/test/scala/cats/tests/TraverseSuite.scala index de0cda385e..05d9784db7 100644 --- a/tests/src/test/scala/cats/tests/TraverseSuite.scala +++ b/tests/src/test/scala/cats/tests/TraverseSuite.scala @@ -4,6 +4,8 @@ package tests import org.scalacheck.Arbitrary import cats.instances.all._ +import kernel.compat.lazyList._ +import compat.lazyList.toLazyList abstract class TraverseSuite[F[_]: Traverse](name: String)(implicit ArbFInt: Arbitrary[F[Int]]) extends CatsSuite { @@ -47,11 +49,11 @@ object TraverseSuite { } class TraverseListSuite extends TraverseSuite[List]("List") -class TraverseStreamSuite extends TraverseSuite[Stream]("Stream") +class TraverseStreamSuite extends TraverseSuite[LazyList]("Stream") class TraverseVectorSuite extends TraverseSuite[Vector]("Vector") class TraverseListSuiteUnderlying extends TraverseSuite.Underlying[List]("List") -class TraverseStreamSuiteUnderlying extends TraverseSuite.Underlying[Stream]("Stream") +class TraverseStreamSuiteUnderlying extends TraverseSuite.Underlying[LazyList]("Stream") class TraverseVectorSuiteUnderlying extends TraverseSuite.Underlying[Vector]("Vector") class TraverseSuiteAdditional extends CatsSuite { @@ -66,7 +68,7 @@ class TraverseSuiteAdditional extends CatsSuite { } test("Traverse[Stream].zipWithIndex stack safety") { - checkZipWithIndexedStackSafety[Stream](_.toStream) + checkZipWithIndexedStackSafety[LazyList](toLazyList) } test("Traverse[Vector].zipWithIndex stack safety") {