Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cross compile replace Stream with LazyList on Scala 2.13 #2904

Merged
merged 9 commits into from
Jun 21, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
34 changes: 20 additions & 14 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -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._
Expand All @@ -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-")
Expand All @@ -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),
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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))
Expand Down
9 changes: 9 additions & 0 deletions core/src/main/scala-2.12-/cats/compat/lazyList.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package cats.compat

object lazyList {

def toLazyList[A](traversableOnce: TraversableOnce[A]): Stream[A] = traversableOnce.toStream

def lazyListString: String = "Stream"

}
Original file line number Diff line number Diff line change
Expand Up @@ -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] {
Expand Down Expand Up @@ -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 {
Expand Down
7 changes: 7 additions & 0 deletions core/src/main/scala-2.13+/cats/compat/lazyList.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package cats.compat

object lazyList {
def toLazyList[A](io: IterableOnce[A]): LazyList[A] = LazyList.from(io)

def lazyListString: String = "LazyList"
}
206 changes: 206 additions & 0 deletions core/src/main/scala-2.13+/cats/instances/lazyList.scala
Original file line number Diff line number Diff line change
@@ -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

}
}
15 changes: 8 additions & 7 deletions core/src/main/scala/cats/data/OneAnd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
}
Expand Down
Loading