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

Added NonEmptyLazyList to replace NonEmptyStream #2941

Merged
merged 40 commits into from
Jul 19, 2019
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
ad616a7
make NonEmptyStreamSuite 2.12- specific
kailuowang Jun 21, 2019
e03f4e4
reformat
kailuowang Jun 21, 2019
03aa5b7
partially converted NonEmptyLazyList from NonEmptyList
nathaniel-may Jun 27, 2019
8e10a99
better TODOs and replaced occurrances of Nil
nathaniel-may Jun 27, 2019
6e90505
first pass at intended impl
nathaniel-may Jun 28, 2019
fcdd483
fixed imports
nathaniel-may Jun 28, 2019
7b6b58c
removed collectFirstSome. Defers to default instance impl
nathaniel-may Jun 28, 2019
57cf435
reduceLeftTo and reduceRightTo now uses same impl from NonEmptyChain
nathaniel-may Jun 28, 2019
14dd3da
refactored instances to pull from existing LazyList instances
nathaniel-may Jun 28, 2019
fe1ac0c
fix compilation
kailuowang Jun 28, 2019
c1d8e76
added missing instances for NonEmptyLazyList
kailuowang Jul 2, 2019
da83de2
moved NonEmptyChain to new structure
kailuowang Jul 2, 2019
bce53d3
NonEmptyVector is moved to new structure
kailuowang Jul 3, 2019
9585857
refactor tests
kailuowang Jul 3, 2019
85dfb8f
refactor use a single base trait
kailuowang Jul 3, 2019
d0519fa
moving NonEmptyList to new structure
kailuowang Jul 3, 2019
35fd2c1
reformat
kailuowang Jul 3, 2019
ecf5a70
maintain BC
kailuowang Jul 3, 2019
a591a23
Merge branch 'master' into refactorNonEmpty
kailuowang Jul 4, 2019
c177384
Update NonEmptyStreamSuite.scala
kailuowang Jul 4, 2019
897b4a9
fix new kind projector
kailuowang Jul 4, 2019
9eb3b26
minor rename to make it more consistent
kailuowang Jul 8, 2019
e97fb20
remove unused file
kailuowang Jul 9, 2019
57e4ea7
Update ScalaVersionSpecific.scala
kailuowang Jul 9, 2019
e8df134
Update ScalaVersionSpecific.scala
kailuowang Jul 9, 2019
fee01b8
correct filename
kailuowang Jul 12, 2019
aafb596
restore NonEmptyList and NonEmptyVector
kailuowang Jul 12, 2019
db03560
more reversal
kailuowang Jul 12, 2019
8769034
refactor shared code between NonEmptyChain and NonEmptyVector
kailuowang Jul 12, 2019
e2f26c2
fix cross compile for NonEmptyVector
kailuowang Jul 12, 2019
87a82ab
reformat
kailuowang Jul 12, 2019
1a16e4f
rename
kailuowang Jul 12, 2019
35468fa
fix BC and compiler warning
kailuowang Jul 12, 2019
f7318a6
make pacakge more restrictive.
kailuowang Jul 12, 2019
8ccbcd7
make package private more restrictive
kailuowang Jul 12, 2019
9f3f9a5
remove old work around no longer needed
kailuowang Jul 16, 2019
f6c0b7d
removed another work around for 2.10 no longer needed
kailuowang Jul 16, 2019
30a31bb
added link to original newtype lib
kailuowang Jul 16, 2019
e614cc6
added more comments
kailuowang Jul 16, 2019
4c0febb
reformat
kailuowang Jul 16, 2019
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package cats

package data

abstract private[data] class ScalaVersionSpecificPackage
378 changes: 378 additions & 0 deletions core/src/main/scala-2.13+/cats/data/NonEmptyLazyList.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,378 @@
package cats
package data

import NonEmptyLazyList.create
import kernel.PartialOrder
import instances.lazyList._

import scala.collection.immutable.TreeSet

object NonEmptyLazyList extends NonEmptyLazyListInstances {
private[data] type Base
private[data] trait Tag extends Any
type Type[+A] <: Base with Tag
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t understand how these are abstract on a concrete object. What is Type?

Copy link
Contributor Author

@kailuowang kailuowang Jul 16, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the newtype encoding (kind of a hack of the Scala type system) that eliminates all runtime overhead. I didn't come up with it. But it has been battle tested cats since 1.0 in NonEmtySet and NonEmptyMap, and later NonEmptyChain

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know the trick, I just didn't know objects could have totally abstract types. Is that intended, or some quirk of the compiler?

Seems to me, anything we an abstract type should have to be abstract.

Do we have this documented somewhere that we can link to? I think it is not at all clear to the reader what the various parts are here and why they are all required:

  1. why do we need Base?
  2. why do we need Tag?
  3. why should the above be private[data] vs private or public?
  4. how is the aliasing of this type to NonEmptyLazyList accomplished? Is that a package alias?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point. Link added.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also cc @LukaJCB to see if he has anything to add to the simple description and the link


private[data] def create[A](s: LazyList[A]): Type[A] =
s.asInstanceOf[Type[A]]

private[data] def unwrap[A](s: Type[A]): LazyList[A] =
s.asInstanceOf[LazyList[A]]

def fromLazyList[A](as: LazyList[A]): Option[NonEmptyLazyList[A]] =
if (as.nonEmpty) Option(create(as)) else None

def fromLazyListUnsafe[A](ll: LazyList[A]): NonEmptyLazyList[A] =
if (ll.nonEmpty) create(ll)
else throw new IllegalArgumentException("Cannot create NonEmptyLazyList from empty LazyList")

def fromNonEmptyList[A](as: NonEmptyList[A]): NonEmptyLazyList[A] =
create(LazyList.from(as.toList))

def fromNonEmptyVector[A](as: NonEmptyVector[A]): NonEmptyLazyList[A] =
create(LazyList.from(as.toVector))

def fromSeq[A](as: Seq[A]): Option[NonEmptyLazyList[A]] =
if (as.nonEmpty) Option(create(LazyList.from(as))) else None

def fromLazyListPrepend[A](a: A, ca: LazyList[A]): NonEmptyLazyList[A] =
create(a +: ca)

def fromLazyListAppend[A](ca: LazyList[A], a: A): NonEmptyLazyList[A] =
create(ca :+ a)

def apply[A](a: => A, as: A*): NonEmptyLazyList[A] =
create(LazyList.concat(LazyList(a), LazyList.from(as)))

implicit def catsNonEmptyLazyListOps[A](value: NonEmptyLazyList[A]): NonEmptyLazyListOps[A] =
new NonEmptyLazyListOps(value)
}

class NonEmptyLazyListOps[A](private val value: NonEmptyLazyList[A]) extends AnyVal {

/**
* Converts this NonEmptyLazyList to a `LazyList`
*/
final def toLazyList: LazyList[A] = NonEmptyLazyList.unwrap(value)

final def map[B](f: A => B): NonEmptyLazyList[B] = create(toLazyList.map(f))

/**
* Returns the last element
*/
final def last: A = toLazyList.last

/**
* Returns all elements but the last
*/
final def init: LazyList[A] = toLazyList.init

/**
* Returns the size of this NonEmptyLazyList
*/
final def size: Int = toLazyList.size

/**
* Returns the length of this NonEmptyLazyList
*/
final def length: Int = toLazyList.length

/**
* Returns a new NonEmptyLazyList consisting of `a` followed by this
*/
final def prepend[AA >: A](a: AA): NonEmptyLazyList[AA] =
create(a #:: toLazyList)

/**
* Alias for [[prepend]].
*/
final def +:[AA >: A](a: AA): NonEmptyLazyList[AA] =
prepend(a)

/**
* Alias for [[prepend]].
*/
final def #::[AA >: A](a: AA): NonEmptyLazyList[AA] =
prepend(a)

/**
* Returns a new NonEmptyLazyList consisting of this followed by `a`
*/
final def append[AA >: A](a: AA): NonEmptyLazyList[AA] =
create(toLazyList :+ a)

/**
* Alias for [[append]].
*/
final def :+[AA >: A](a: AA): NonEmptyLazyList[AA] =
append(a)

/**
* concatenates this with `ll`
*/
final def concat[AA >: A](ll: LazyList[AA]): NonEmptyLazyList[AA] =
create(toLazyList ++ ll)

/**
* Concatenates this with `nell`
*/
final def concatNell[AA >: A](nell: NonEmptyLazyList[AA]): NonEmptyLazyList[AA] =
create(toLazyList ++ nell.toLazyList)

/**
* Alias for concatNell
*/
final def ++[AA >: A](nell: NonEmptyLazyList[AA]): NonEmptyLazyList[AA] =
concatNell(nell)

/**
* Appends the given LazyList
*/
final def appendLazyList[AA >: A](nell: LazyList[AA]): NonEmptyLazyList[AA] =
if (nell.isEmpty) value
else create(toLazyList ++ nell)

/**
* Alias for `appendLazyList`
*/
final def :++[AA >: A](c: LazyList[AA]): NonEmptyLazyList[AA] =
appendLazyList(c)

/**
* Prepends the given LazyList
*/
final def prependLazyList[AA >: A](c: LazyList[AA]): NonEmptyLazyList[AA] =
if (c.isEmpty) value
else create(c ++ toLazyList)

/**
* Prepends the given NonEmptyLazyList
*/
final def prependNell[AA >: A](c: NonEmptyLazyList[AA]): NonEmptyLazyList[AA] =
create(c.toLazyList ++ toLazyList)

/**
* Alias for `prependNell`
*/
final def ++:[AA >: A](c: NonEmptyLazyList[AA]): NonEmptyLazyList[AA] =
prependNell(c)

/**
* Converts this NonEmptyLazyList to a `NonEmptyList`.
*/ // TODO also add toNonEmptyLazyList to NonEmptyList?
final def toNonEmptyList: NonEmptyList[A] =
NonEmptyList.fromListUnsafe(toLazyList.toList)

/**
* Converts this LazyList to a `NonEmptyVector`.
*/
final def toNonEmptyVector: NonEmptyVector[A] =
NonEmptyVector.fromVectorUnsafe(toLazyList.toVector)

/**
* Returns the first element
*/
final def head: A = toLazyList.head

/**
* Returns all but the first element
*/
final def tail: LazyList[A] = toLazyList.tail

/**
* Tests if some element is contained in this NonEmptyLazyList
*/
final def contains(a: A)(implicit A: Eq[A]): Boolean =
toLazyList.contains(a)

/**
* Tests whether a predicate holds for all elements
*/
final def forall(p: A => Boolean): Boolean =
toLazyList.forall(p)

/**
* Tests whether a predicate holds for at least one element of this LazyList
*/
final def exists(f: A => Boolean): Boolean =
toLazyList.exists(f)

/**
* Returns the first value that matches the given predicate.
*/
final def find(f: A => Boolean): Option[A] =
toLazyList.find(f)

/**
* Returns a new `LazyList` containing all elements where the result of `pf` is final defined.
*/
final def collect[B](pf: PartialFunction[A, B]): LazyList[B] =
toLazyList.collect(pf)

/**
* Finds the first element of this `NonEmptyLazyList` for which the given partial
* function is defined, and applies the partial function to it.
*/
final def collectLazyList[B](pf: PartialFunction[A, B]): Option[B] = toLazyList.collectFirst(pf)

/**
* Filters all elements of this NonEmptyLazyList that do not satisfy the given predicate.
*/
final def filter(p: A => Boolean): LazyList[A] = toLazyList.filter(p)

/**
* Filters all elements of this NonEmptyLazyList that satisfy the given predicate.
*/
final def filterNot(p: A => Boolean): LazyList[A] = filter(t => !p(t))

/**
* Left-associative fold using f.
*/
final def foldLeft[B](b: B)(f: (B, A) => B): B =
toLazyList.foldLeft(b)(f)

/**
* Right-associative fold using f.
*/
final def foldRight[B](z: B)(f: (A, B) => B): B =
toLazyList.foldRight(z)(f)

/**
* Left-associative reduce using f.
*/
final def reduceLeft(f: (A, A) => A): A =
toLazyList.reduceLeft(f)

/**
* Apply `f` to the "initial element" of this LazyList and lazily combine it
* with every other value using the given function `g`.
*/
final def reduceLeftTo[B](f: A => B)(g: (B, A) => B): B = {
val iter = toLazyList.iterator
var result = f(iter.next)
while (iter.hasNext) { result = g(result, iter.next) }
result
}

/**
* Right-associative reduce using f.
*/
final def reduceRight[AA >: A](f: (A, AA) => AA): AA =
toLazyList.reduceRight(f)

/**
* Apply `f` to the "initial element" of this NonEmptyLazyList and
* lazily combine it with every other value using the given function `g`.
*/
final def reduceRightTo[B](f: A => B)(g: (A, B) => B): B = {
val iter = toLazyList.reverseIterator
var result = f(iter.next)
while (iter.hasNext) { result = g(iter.next, result) }
result
}

/**
* Reduce using the Semigroup of A
*/
final def reduce[AA >: A](implicit S: Semigroup[AA]): AA =
S.combineAllOption(iterator).get

/**
* Applies the supplied function to each element and returns a new NonEmptyLazyList from the concatenated results
*/
final def flatMap[B](f: A => NonEmptyLazyList[B]): NonEmptyLazyList[B] =
create(toLazyList.flatMap(f.andThen(_.toLazyList)))

/**
* Zips this `NonEmptyLazyList` with another `NonEmptyLazyList` and applies a function for each pair of elements
*/
final def zipWith[B, C](b: NonEmptyLazyList[B])(f: (A, B) => C): NonEmptyLazyList[C] =
create(toLazyList.zip(b.toLazyList).map { case (a, b) => f(a, b) })

/**
* Zips each element of this `NonEmptyLazyList` with its index
*/
final def zipWithIndex: NonEmptyLazyList[(A, Int)] =
create(toLazyList.zipWithIndex)

final def iterator: Iterator[A] = toLazyList.iterator

final def reverseIterator: Iterator[A] = toLazyList.reverseIterator

/**
* Reverses this `NonEmptyLazyList`
*/
final def reverse: NonEmptyLazyList[A] =
create(toLazyList.reverse)

/**
* Remove duplicates. Duplicates are checked using `Order[_]` instance.
*/
def distinct[AA >: A](implicit O: Order[AA]): NonEmptyLazyList[AA] = {
implicit val ord = O.toOrdering

val buf = LazyList.newBuilder[AA]
toLazyList.foldLeft(TreeSet.empty[AA]) { (elementsSoFar, a) =>
if (elementsSoFar(a)) elementsSoFar
else {
buf += a; elementsSoFar + a
}
}

create(buf.result())
}
}

sealed abstract private[data] class NonEmptyLazyListInstances extends NonEmptyLazyListInstances1 {

implicit val catsDataInstancesForNonEmptyLazyList
: Bimonad[NonEmptyLazyList] with NonEmptyTraverse[NonEmptyLazyList] with SemigroupK[NonEmptyLazyList] =
new AbstractNonEmptyInstances[LazyList, NonEmptyLazyList] {

def extract[A](fa: NonEmptyLazyList[A]): A = fa.head

def nonEmptyTraverse[G[_]: Apply, A, B](fa: NonEmptyLazyList[A])(f: A => G[B]): G[NonEmptyLazyList[B]] =
Foldable[LazyList]
.reduceRightToOption[A, G[LazyList[B]]](fa.tail)(a => Apply[G].map(f(a))(LazyList.apply(_))) { (a, lglb) =>
Apply[G].map2Eval(f(a), lglb)(_ +: _)
}
.map {
case None => Apply[G].map(f(fa.head))(h => create(LazyList(h)))
case Some(gtail) => Apply[G].map2(f(fa.head), gtail)((h, t) => create(LazyList(h) ++ t))
}
.value

def reduceLeftTo[A, B](fa: NonEmptyLazyList[A])(f: A => B)(g: (B, A) => B): B = fa.reduceLeftTo(f)(g)

def reduceRightTo[A, B](fa: NonEmptyLazyList[A])(f: A => B)(g: (A, cats.Eval[B]) => cats.Eval[B]): cats.Eval[B] =
Eval.defer(fa.reduceRightTo(a => Eval.now(f(a))) { (a, b) =>
Eval.defer(g(a, b))
})
}

implicit def catsDataOrderForNonEmptyLazyList[A: Order]: Order[NonEmptyLazyList[A]] =
Order[LazyList[A]].asInstanceOf[Order[NonEmptyLazyList[A]]]

implicit def catsDataSemigroupForNonEmptyLazyList[A]: Semigroup[NonEmptyLazyList[A]] =
Semigroup[LazyList[A]].asInstanceOf[Semigroup[NonEmptyLazyList[A]]]

implicit def catsDataShowForNonEmptyLazyList[A](implicit A: Show[A]): Show[NonEmptyLazyList[A]] =
Show.show[NonEmptyLazyList[A]](nec => s"NonEmpty${Show[LazyList[A]].show(nec.toLazyList)}")

}

sealed abstract private[data] class NonEmptyLazyListInstances1 extends NonEmptyLazyListInstances2 {

implicit def catsDataHashForNonEmptyLazyList[A: Hash]: Hash[NonEmptyLazyList[A]] =
Hash[LazyList[A]].asInstanceOf[Hash[NonEmptyLazyList[A]]]

}

sealed abstract private[data] class NonEmptyLazyListInstances2 extends NonEmptyLazyListInstances3 {
implicit def catsDataPartialOrderForNonEmptyLazyList[A: PartialOrder]: PartialOrder[NonEmptyLazyList[A]] =
PartialOrder[LazyList[A]].asInstanceOf[PartialOrder[NonEmptyLazyList[A]]]
}

sealed abstract private[data] class NonEmptyLazyListInstances3 {
implicit def catsDataEqForNonEmptyLazyList[A: Eq]: Eq[NonEmptyLazyList[A]] =
Eq[LazyList[A]].asInstanceOf[Eq[NonEmptyLazyList[A]]]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package cats

package data

abstract private[data] class ScalaVersionSpecificPackage {
type NonEmptyLazyList[+A] = NonEmptyLazyList.Type[A]
}
Loading