Creating a lazy list that ends

I am trying to create a lazy list which ends on a certain condition. I have come up with the following code.

def fetch: Either[MyError, Seq[MyData]] = ???
val lazyData: LazyList[Either[MyError, Seq[MyData]]] = fetch #:: {
            if(!isSeenAllData(dataTracker)) lazyData
            else Nil
        }
lazyData.filter(_.fold(fa = {_ => true}, fb =  _.nonEmpty))
  • It fails with a compilation error value #:: is not a member of (lazyData : ....
  • Is this code even the correct way of building a lazy list which ends on a certain condition?

Nil is an empty List. For an empty LazyList you could go with LazyList.empty. Or you can probably rewrite this as

val lazyData = LazyList.continually(fetch).takeWhile(_ => !isSeenAllData(dataTracker))
2 Likes

Using LazyList.empty gives the error

lazyData is a forward reference extending over the definition of lazyData

Errr… I’m not really a fan nor expert of these recursive Stream/LazyList values, but I’m going to guess that lazyData has to be a lazy val if it’s recursive.

1 Like

yeah you are right. But this document does it without the lazy keyword - https://www.scala-lang.org/api/current/scala/collection/immutable/LazyList.html

Local vals cannot reference themselves, but instance (class/object level) vals can. For some reason.

Anyway, I suspect your solution does not do what you expect it will do. I suspect it will just be an infinite sequence of the first value that got fetched. It’s why I prefer not to write recursive LazyLists to begin with :sweat_smile: It’s a fun academic exercise to write something (like a fibonacci sequence) as a recursive LazyList but in “real” code you’re more likely to just shoot yourself in the foot while you could have saved yourself a lot of time by using one of the helper methods.

2 Likes

Using LazyList this way looks suspicious. What if you call size()? Your use case sounds like one for an Iterator.

actually, it does what is expected of it:) It does not create an infinite sequence of the first value

@Jasper-M actually, you are right! It does go on an infinite loop (I thought I tried it out and it worked).

That is strange because when I try it on some dummy code of the same shape, I do get an infinite sequence of 1 repeating value.

oh, is your fetch a val or a def?

It’s math.random() :sweat_smile:

The reason that I cannot use the continually helper method is because the fetch function does a side effect (marks the fetched data as seen) and the takeWhile function (checks if all data has been seen) will not allow the last element to pass through.

class Fetcher() {
    type MyData = Int
    private var called = 0
    def isSeenAllData = called > 5
    def fetch: Either[Exception, Seq[MyData]] =
      if (isSeenAllData) Left(new Exception("too many calls!"))
      else {
        called += 1
        Right(Seq(1 to called: _*))
      }
  }
>> defined class Fetcher

val lazyList = LazyList.unfold(new Fetcher()) { f => if (f.isSeenAllData) None else Some((f.fetch, f)) }
>> lazyList: LazyList[Either[Exception, Seq[Int]]] = LazyList(Right(List(1)), Right(List(1, 2)), Right(List(1, 2, 3)), Right(List(1, 2, 3, 4)), Right(List(1, 2, 3, 4, 5)), Right(List(1, 2, 3, 4, 5, 6)))

To make sure the list is actually lazy

class LazyCheck(fetcher: Fetcher) {
    val lazyList = LazyList.unfold(fetcher) { f => if (f.isSeenAllData) None else Some((f.fetch, f)) }
  }
defined class LazyCheck

@ val f = new Fetcher
f: Fetcher = ammonite.$sess.cmd5$Fetcher@4b9b5da5

@ f.isSeenAllData
res9: Boolean = false

@ val check = new LazyCheck(f)
check: LazyCheck = ammonite.$sess.cmd7$LazyCheck@c19bb2a

@ f.isSeenAllData
res11: Boolean = false

@ check.lazyList.headOption
res12: Option[Either[Exception, Seq[Int]]] = Some(Right(List(1)))

@ f.isSeenAllData
res13: Boolean = false

@ check.lazyList
res14: LazyList[Either[Exception, Seq[Int]]] = LazyList(Right(List(1)), Right(List(1, 2)), Right(List(1, 2, 3)), Right(List(1, 2, 3, 4)), Right(List(1, 2, 3, 4, 5)), Right(List(1, 2, 3, 4, 5, 6)))

@ f.isSeenAllData
res15: Boolean = true
1 Like

code snippet? did you mean something like Iterator.continually(fetch)

LazyList.unfold should work well. It lets you stop generating elements at any moment.

2 Likes

okie, will try it a shot:)