Проблема вызвана InfiniteStream.filter
, он создает хвостовой фильтр как ленивое значение, но затем немедленно обращается к нему, что вынуждает оценить значение.Это приводит к тому, что весь поток оценивается как рекурсивные вызовы, взрывающие стек.
Ленивый val задерживает выполнение выражения, используемого для создания переменной, до тех пор, пока к ней не будет получен доступ.Поэтому вам нужно отложить доступ к tail.filter(predicate)
до тех пор, пока пользователь потока не получит доступ к хвосту.
Самый простой и более функциональный способ добиться этого - реализовать фильтр с представлением.То есть фильтр возвращает новый поток, который фильтрует только хвост по требованию.
EG
class FilterStream[+A] private (predicate: predicate: A => Boolean, stream: MyStream) extends MyStream[A] {
override def head: A = stream.head
override def tail: MyStream[A] = FilterStream.dropWhile(!predicate(_), stream)
}
object FilterStream {
def apply[A](predicate: predicate: A => Boolean, stream: MyStream[A]): MyStream[A] = {
new FilterStream(predicate, dropWhile(!predicate(_), stream))
}
@tailrec
def dropWhile[A](predicate: predicate: A => Boolean, stream: MyStream[A]): MyStream[A] = {
if (stream.isEmpty || predicate(stream.head)) stream
else dropWhile(predicate, stream.tail)
}
}
Наконец, вы должны рассмотреть возможность реализации пустого потока со своим собственным типом и объектом по многим причинам, но такжетак что вы можете завершить бесконечный поток, если ваш generator
решит, что он хочет.
object Nil extends MyStream[Nothing] {
override def head: A = throw NoSuchElement
override def tail: MyStream[A] = throw NoSuchElement
}
Голова и хвост всегда небезопасные методы, еще одним улучшением было бы использование классов case для раскрытия формы вашего потока.тогда пользователи будут сопоставлять паттерны в потоке.Это защитит ваших пользователей от необходимости использовать небезопасные методы, такие как head
и tail
.