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

Redesign NonEmptyLazyList to be maximally lazy #4504

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
175 changes: 141 additions & 34 deletions core/src/main/scala-2.13+/cats/data/NonEmptyLazyList.scala
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,13 @@ object NonEmptyLazyList extends NonEmptyLazyListInstances {
s.asInstanceOf[LazyList[A]]

def fromLazyList[A](as: LazyList[A]): Option[NonEmptyLazyList[A]] =
if (as.nonEmpty) Option(create(as)) else None
if (as.nonEmpty) Some(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 fromLazyListUnsafe[A](ll: LazyList[A]): NonEmptyLazyList[A] = {
@inline def ex = new IllegalArgumentException("Cannot create NonEmptyLazyList from empty LazyList")
if (ll.knownSize == 0) throw ex
else create(LazyList.empty #::: { if (ll.isEmpty) throw ex else ll })
}
NthPortal marked this conversation as resolved.
Show resolved Hide resolved

def fromNonEmptyList[A](as: NonEmptyList[A]): NonEmptyLazyList[A] =
create(LazyList.from(as.toList))
Expand All @@ -62,14 +64,67 @@ object NonEmptyLazyList extends NonEmptyLazyListInstances {
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 fromLazyListPrepend[A](a: => A, ll: => LazyList[A]): NonEmptyLazyList[A] =
create(a #:: ll)

@deprecated("Use overload with by-name args", "2.11.0")
def fromLazyListPrepend[A]()(a: A, ll: LazyList[A]): NonEmptyLazyList[A] =
fromLazyListPrepend(a, ll)

def fromLazyListAppend[A](ll: => LazyList[A], a: => A): NonEmptyLazyList[A] =
create(LazyList.empty #::: ll #::: a #:: LazyList.empty)

def fromLazyListAppend[A](ca: LazyList[A], a: A): NonEmptyLazyList[A] =
create(ca :+ a)
@deprecated("Use overload with by-name args", "2.11.0")
def fromLazyListAppend[A]()(ll: LazyList[A], a: A): NonEmptyLazyList[A] =
fromLazyListAppend(ll, a)

def apply[A](a: => A, as: A*): NonEmptyLazyList[A] =
create(LazyList.concat(LazyList(a), LazyList.from(as)))
create(a #:: LazyList.from(as))
NthPortal marked this conversation as resolved.
Show resolved Hide resolved

/**
* Wraps a `LazyList` that may or may not be empty so that individual
* elements or `NonEmptyLazyList`s can be prepended to it to construct a
* result that is guaranteed not to be empty without evaluating any elements.
*/
def maybe[A](ll: => LazyList[A]): Maybe[A] = new Maybe(LazyList.empty #::: ll)

/**
* Wrapper around a `LazyList` that may or may not be empty so that individual
* elements or `NonEmptyLazyList`s can be prepended to it to construct a
* result that is guaranteed not to be empty without evaluating any elements.
*/
final class Maybe[A] private[NonEmptyLazyList] (private val ll: LazyList[A]) extends AnyVal {

/** Prepends a single element, yielding a `NonEmptyLazyList`. */
def #::[AA >: A](elem: => AA): NonEmptyLazyList[AA] =
create(elem #:: ll)

/** Prepends a `LazyList`, yielding another [[Maybe]]. */
def #:::[AA >: A](prefix: => LazyList[AA]): Maybe[AA] =
new Maybe(LazyList.empty #::: prefix #::: ll)

/** Prepends a `NonEmptyLazyList`, yielding a `NonEmptyLazyList`. */
def #:::[AA >: A](prefix: => NonEmptyLazyList[AA])(implicit d: DummyImplicit): NonEmptyLazyList[AA] =
create(LazyList.empty #::: prefix.toLazyList #::: ll)
}

final class Deferrer[A] private[NonEmptyLazyList] (private val nell: () => NonEmptyLazyList[A]) extends AnyVal {

/** Prepends a single element. */
def #::[AA >: A](elem: => AA): NonEmptyLazyList[AA] =
create(elem #:: nell().toLazyList)

/** Prepends a `LazyList`. */
def #:::[AA >: A](prefix: => LazyList[AA]): NonEmptyLazyList[AA] =
create(LazyList.empty #::: prefix #::: nell().toLazyList)

/** Prepends a `NonEmptyLazyList`. */
def #:::[AA >: A](prefix: => NonEmptyLazyList[AA])(implicit d: DummyImplicit): NonEmptyLazyList[AA] =
create(LazyList.empty #::: prefix.toLazyList #::: nell().toLazyList)
}

implicit def toDeferrer[A](nell: => NonEmptyLazyList[A]): Deferrer[A] =
new Deferrer(() => nell)

implicit def catsNonEmptyLazyListOps[A](value: NonEmptyLazyList[A]): NonEmptyLazyListOps[A] =
new NonEmptyLazyListOps(value)
NthPortal marked this conversation as resolved.
Show resolved Hide resolved
Expand Down Expand Up @@ -110,18 +165,16 @@ class NonEmptyLazyListOps[A](private val value: NonEmptyLazyList[A])
* Returns a new NonEmptyLazyList consisting of `a` followed by this
*/
final def prepend[AA >: A](a: AA): NonEmptyLazyList[AA] =
create(a #:: toLazyList)
create(toLazyList.prepended(a))
NthPortal marked this conversation as resolved.
Show resolved Hide resolved

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

/**
* Alias for [[prepend]].
*/
final def #::[AA >: A](a: AA): NonEmptyLazyList[AA] =
@deprecated("use Deferrer construction instead")
final def #::[AA >: A]()(a: AA): NonEmptyLazyList[AA] =
prepend(a)
NthPortal marked this conversation as resolved.
Show resolved Hide resolved

/**
Expand All @@ -137,54 +190,108 @@ class NonEmptyLazyListOps[A](private val value: NonEmptyLazyList[A])
append(a)

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

@deprecated("Use overload with by-name args", "2.11.0")
final private[data] def concat[AA >: A]()(ll: LazyList[AA]): NonEmptyLazyList[AA] =
concat(ll)

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

/**
* Alias for concatNell
* Concatenates this with `nell`; equivalent to `appendNell`
*/
final def ++[AA >: A](nell: NonEmptyLazyList[AA]): NonEmptyLazyList[AA] =
final def concatNell[AA >: A](nell: => NonEmptyLazyList[AA]): NonEmptyLazyList[AA] =
appendNell(nell)

@deprecated("Use overload with by-name args", "2.11.0")
final private[data] def concatNell[AA >: A]()(nell: NonEmptyLazyList[AA]): NonEmptyLazyList[AA] =
concatNell(nell)

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

@deprecated("Use overload with by-name args", "2.11.0")
final private[data] def ++[AA >: A]()(ll: NonEmptyLazyList[AA]): NonEmptyLazyList[AA] =
++(ll)

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

@deprecated("Use overload with by-name args", "2.11.0")
final private[data] def appendLazyList[AA >: A]()(ll: LazyList[AA]): NonEmptyLazyList[AA] =
appendLazyList(ll)

/**
* Alias for `appendLazyList`
*/
final def :++[AA >: A](c: LazyList[AA]): NonEmptyLazyList[AA] =
appendLazyList(c)
final def :++[AA >: A](ll: => LazyList[AA]): NonEmptyLazyList[AA] =
appendLazyList(ll)
NthPortal marked this conversation as resolved.
Show resolved Hide resolved

@deprecated("Use overload with by-name args", "2.11.0")
final private[data] def :++[AA >: A]()(ll: LazyList[AA]): NonEmptyLazyList[AA] =
appendLazyList(ll)

/**
* Prepends the given NonEmptyLazyList
*/
final def appendNell[AA >: A](nell: => NonEmptyLazyList[AA]): NonEmptyLazyList[AA] =
create(toLazyList #::: nell.toLazyList)

/**
* Alias for `appendNell`
*/
final def :++[AA >: A](nell: => NonEmptyLazyList[AA])(implicit d: DummyImplicit): NonEmptyLazyList[AA] =
appendNell(nell)

@deprecated("Use overload with by-name args", "2.11.0")
final private[data] def ++:[AA >: A]()(ll: NonEmptyLazyList[AA]): NonEmptyLazyList[AA] =
++:(ll)

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

@deprecated("Use overload with by-name args", "2.11.0")
final private[data] def prependLazyList[AA >: A]()(ll: LazyList[AA]): NonEmptyLazyList[AA] =
prependLazyList(ll)

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

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

@deprecated("Use overload with by-name args", "2.11.0")
final private[data] def prependNell[AA >: A]()(nell: NonEmptyLazyList[AA]): NonEmptyLazyList[AA] =
prependNell(nell)

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

/**
* Converts this NonEmptyLazyList to a `NonEmptyList`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,26 @@ class NonEmptyLazyListSuite extends NonEmptyCollectionSuite[LazyList, NonEmptyLa

(1 to 100).sum === sum
}

test("NonEmptyLazyList#appendLazyList is properly lazy") {
var evaluated = false
val ll = { evaluated = true; 1 } #:: LazyList.empty
val _ = NonEmptyLazyList(0).appendLazyList(ll)
assert(!evaluated)
}

test("NonEmptyLazyList#prependLazyList is properly lazy") {
var evaluated = false
val ll = { evaluated = true; 0 } #:: LazyList.empty
val _ = NonEmptyLazyList(1).prependLazyList(ll)
assert(!evaluated)
}

test("NonEmptyLazyList.apply is properly lazy") {
var evaluated = false
val _ = NonEmptyLazyList({ evaluated = true; 0 }, Nil: _*)
assert(!evaluated)
}
}

class ReducibleNonEmptyLazyListSuite extends ReducibleSuite[NonEmptyLazyList]("NonEmptyLazyList") {
Expand Down