Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
kailuowang committed Jun 20, 2019
1 parent 1db391d commit 8611840
Show file tree
Hide file tree
Showing 10 changed files with 196 additions and 197 deletions.
1 change: 1 addition & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/scala/cats/instances/all.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ trait AllInstances
with SetInstances
with SortedMapInstances
with SortedSetInstances
with StreamInstances
with LazyListInstances
with StringInstances
with SymbolInstances
with TryInstances
Expand Down
185 changes: 185 additions & 0 deletions core/src/main/scala/cats/instances/lazyList.scala
Original file line number Diff line number Diff line change
@@ -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

This comment has been minimized.

Copy link
@johnynek

johnynek Jun 20, 2019

Contributor

why the force here? I don't see why that is needed, and it breaks laziness, no?

This comment has been minimized.

Copy link
@kailuowang

kailuowang Jun 20, 2019

Author Contributor

without it the stacksafety test fails. I added them because I saw these in LazyList doc
I doubt this is the right way, but it's not obvious what would be.

This comment has been minimized.

Copy link
@johnynek

johnynek Jun 20, 2019

Contributor

I haven't worked with LazyList much yet, but I think even the head is lazy, an here that won't be needed since we have to evaluate fn(a).

Maybe:

val newFront = fn(a)
stack = newFront #::: tail 

This comment has been minimized.

Copy link
@kailuowang

kailuowang Jun 20, 2019

Author Contributor

also to note that without the force, the lazyiness is different (less optimally lazy) according to the foldableAdditionalSuites

This comment has been minimized.

Copy link
@kailuowang

kailuowang Jun 20, 2019

Author Contributor

Right. There are several such places a head is appended. I shall review them all.

This comment has been minimized.

Copy link
@johnynek

johnynek Jun 20, 2019

Contributor

Here's a big problem:

scala> val z = 1 #:: (sys.error("boom"): LazyList[Int])
z: scala.collection.immutable.LazyList[Int] = LazyList(<not computed>)

scala> z.isEmpty
java.lang.RuntimeException: boom
  at scala.sys.package$.error(package.scala:29)
  at .$anonfun$z$1(<console>:1)
  at scala.collection.immutable.LazyList$Deferrer$.$anonfun$$hash$colon$colon$extension$1(LazyList.scala:1109)
  at scala.collection.immutable.LazyList.scala$collection$immutable$LazyList$$state$lzycompute(LazyList.scala:219)
  at scala.collection.immutable.LazyList.scala$collection$immutable$LazyList$$state(LazyList.scala:218)
  at scala.collection.immutable.LazyList.isEmpty(LazyList.scala:229)
  ... 36 elided

looks like asking if a list is empty whose tail blows up, will blow up. That wasn't true before. Not sure a way around that.

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

}
}
2 changes: 1 addition & 1 deletion core/src/main/scala/cats/instances/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/scala/cats/instances/parallel.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
Loading

0 comments on commit 8611840

Please sign in to comment.