-
Notifications
You must be signed in to change notification settings - Fork 21
Determine how lazy LazyList should be #11307
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
Comments
Regarding point 1: I am happy to maintain |
Thank you very much for writing this up! The design definitely needs to be finalized before RC1, which is scheduled for end of January. It sounds to me like going for a strict |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
I believe my opinion on this question is already clear to anyone who's read my various comments in the various issues, but I'll state it unambiguously here. I think A lazy head clearly has more overhead and a higher implementation complexity, and as was mentioned we don't have any use case. |
I believe I have come up with a use case (point 3). You have a text file, and you read the lines into a val lines: LazyList[String] = ???
val analysed = lines map doExpensiveAnalysis However, the result of the analysis may tell you that the following val filtered = {
def next(elems: LazyList[Elem]): LazyList[Elem] =
if (elems.isEmpty) elems
else {
val head = elems.head
val skip = head.skip
head #:: next(elems.drop(skip + 1))
}
next(analysed)
} Without a lazy var skip = 0
val filtered = lines flatMap { line =>
if (skip > 0) {
skip -= 1
Nil
} else {
val elem = doExpensiveAnalysis(line)
skip = elem.skipCount
elem :: Nil
}
} If Or perhaps you would like to do something with the last analysed line in the file matching some predicate, and then proceed to perform other operations (perhaps like the one above) starting from the front of the // side note: why isn't there a `findLast`?
val lastMatchingIdx = analysed.lastIndexWhere(somePredicate)
val lastMatching = analysed(lastMatchingIdx) Without a lazy |
Thanks, good to have an example! An alternative is to keep a reference to the original non-mapped I think as long as a lazy head "only" provides some convenience (and that doesn't seem to come up too often), it would be better to opt for the simpler model. If there was a more serious limitation like the one we had with 2.12 Stream (#10696), that would be a much stronger argument. |
I also tend to make |
I've run into a scenario where I wanted Here's an example of what happens for Scala 2.12. I don't want side effects when calling scala> :paste
// Entering paste mode (ctrl-D to finish)
val i = new Iterator[Int] {
var x = 0
override def next(): Int = {
val currentX = x
x += 1
println(currentX)
currentX
}
override def hasNext: Boolean = true
}
// Exiting paste mode, now interpreting.
i: Iterator[Int]{def x: Int; def x_=(x$1: Int): Unit} = <iterator>
scala> i.toStream
0
res7: scala.collection.immutable.Stream[Int] = Stream(0, ?) |
@shawjef3 What you're describing is "form 1" of laziness as defined in the first post of this thread, and that is a given for |
Is there any use case of lazy head which can't be solved by |
@joroKr21 no, but that introduces another layer of indirection (and boxing and dependent load); if it's a strong use case, we should have it not require extra boxing and indirection. Edit: it would also make it a pain to use, as all method calls would need another indirection of mapping or retrieving the lazy-boxed value. |
Requesting input from others who have been involved with pinging @hrhino @julienrf @szeiger @SethTisue @paulp. I am currently leaning the same way as the current rough consensus, to make |
i often hear people complaining about Stream head being strict, and then some people started pointing out that it's fixed in LazyList |
No it doesn't. When people complain about the head being strict in |
Given the high complexity of the new implementation I also agree with forcing elements when their existence has been determined. It would also resolve the problems with |
I'm fine with dropping form 2. It turned out to be a bigger can of worms that any of us anticipated. |
@NthPortal also I think there's now sufficient consensus that you can go forward with this. |
|
Looks like |
you need scala> lazy val ones: LazyList[Int] = 1 #:: ones
ones: LazyList[Int] = <lazy>
scala> ones.head
java.lang.StackOverflowError
at .ones(<console>:1)
at .ones$lzycompute(<console>:1)
at .ones(<console>:1)
at .ones$lzycompute(<console>:1)
scala> lazy val nats: LazyList[Int] = 1 #:: nats.map(_ + 1)
nats: LazyList[Int] = <lazy>
scala> nats.head
java.lang.StackOverflowError
at .nats$lzycompute(<console>:1)
at .nats(<console>:1)
at .nats$lzycompute(<console>:1)
at .nats(<console>:1) |
see scala/scala#7990 for a way that could potentially eliminate |
@szeiger I'm not completely following how it can eliminate |
Oh, right, you need both operands to be lazy. I wonder if extension methods could be used. The current documentation doesn't mention it. |
if I understand correctly, |
Background
(quoted from #11105)
Form 1 is one of the primary reasons for the creation of
LazyList
, is generally maintained throughout operations on it, and I think is is generally agreed to be of very high value.Form 2 is less obviously valuable. Historically, it exists because the original implementation of
LazyList
had a strict state (empty or cons), and lazyhead
andtail
(likeStream
, but with a lazyhead
as well); when the design ofLazyList
was changed to have a lazy state and stricttail
(because lazytail
doesn't add anything once the state is lazy), the lazyhead
was still kept.During various discussions on various issues, it has been noted that the fact that
LazyList
's supports form 2 of lazinessLazyList
is created in a fashion (e.g. from anIterator
) that does not have form 2 of lazinessfilter
,dropWhile
) which by their nature must evaluate elements sequentially in order to determine the next elementThis yields the question (quoted from @sjrd at scala/scala#6880 (comment)):
(see also #11089 (comment))
Issue and Investigation Areas
Should
LazyList
support form 2 of laziness by having a lazyhead
? There are a few points to think about in this regard:head
?LazyList
does not support form 2 of laziness, the fact that some methods (such asmap
) support form 2 of laziness is valuable because it allows performing independently lazy expensive operations?LazyList
(in contrast to what is discussed in the previous question)? I and several others have yet to come up with one.I think that if we cannot come up any compelling use cases for form 2 of laziness, taking into account how frequently it is not present anyway, then
LazyList
should be changed to have a stricthead
and not support form 2 of laziness. I say this despite the fact that I have invested significant effort into writing comprehensive tests which ensure that all methods which can preserve/support form 2 of laziness do so. Once 2.13 is out, it will be difficult to changeLazyList
's behaviour even in subsequent releases of Scala because it would be a significant change in semantics (much like the wayStream
cannot be a deprecated type alias forLazyList
).Consensus
Form 2 of laziness should be removed, and
head
made strict.I don't know if this issue actually needs to be resolved by 2.13.0-RC1, but it definitely needs to be resolved by the final release of 2.13.0, and it would probably be better to resolve it at least a few release candidates beforehand.The text was updated successfully, but these errors were encountered: