Да, вы можете. Хитрость заключается в том, чтобы использовать хвостовые рекурсивные методы, чтобы кадр локального стека содержал единственную ссылку на экземпляр Stream
. Так как метод является хвостово-рекурсивным, локальная ссылка на предыдущую головку Stream
будет стерта, как только он рекурсивно вызовет себя, что позволит GC собирать начало Stream
по мере продвижения.
Welcome to Scala version 2.9.0.r23459-b20101108091606 (Java HotSpot(TM) Server VM, Java 1.6.0_20).
Type in expressions to have them evaluated.
Type :help for more information.
scala> import collection.immutable.Stream
import collection.immutable.Stream
scala> import annotation.tailrec
import annotation.tailrec
scala> @tailrec def last(s: Stream[Int]): Int = if (s.tail.isEmpty) s.head else last(s.tail)
last: (s: scala.collection.immutable.Stream[Int])Int
scala> last(Stream.range(0, 100000000))
res2: Int = 99999999
Кроме того, вы должны убедиться, что объект, который вы передаете методу last
выше, имеет только одну ссылку в стеке. Если вы сохраняете Stream
в локальной переменной или значении, он не будет собирать мусор при вызове метода last
, поскольку его аргумент - не единственная ссылка, оставленная на Stream
. В приведенном ниже коде не хватает памяти.
scala> val s = Stream.range(0, 100000000)
s: scala.collection.immutable.Stream[Int] = Stream(0, ?)
scala> last(s)
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at sun.net.www.ParseUtil.encodePath(ParseUtil.java:84)
at sun.misc.URLClassPath$JarLoader.checkResource(URLClassPath.java:674)
at sun.misc.URLClassPath$JarLoader.getResource(URLClassPath.java:759)
at sun.misc.URLClassPath.getResource(URLClassPath.java:169)
at java.net.URLClassLoader$1.run(URLClassLoader.java:194)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
at java.lang.ClassLoader.loadClass(ClassLoader.java:248)
at scala.tools.nsc.Interpreter$Request$$anonfun$onErr$1$1.apply(Interpreter.scala:978)
at scala.tools.nsc.Interpreter$Request$$anonfun$onErr$1$1.apply(Interpreter.scala:976)
at scala.util.control.Exception$Catch.apply(Exception.scala:80)
at scala.tools.nsc.Interpreter$Request.loadAndRun(Interpreter.scala:984)
at scala.tools.nsc.Interpreter.loadAndRunReq$1(Interpreter.scala:579)
at scala.tools.nsc.Interpreter.interpret(Interpreter.scala:599)
at scala.tools.nsc.Interpreter.interpret(Interpreter.scala:576)
at scala.tools.nsc.InterpreterLoop.reallyInterpret$1(InterpreterLoop.scala:472)
at scala.tools.nsc.InterpreterLoop.interpretStartingWith(InterpreterLoop.scala:515)
at scala.tools.nsc.InterpreterLoop.command(InterpreterLoop.scala:362)
at scala.tools.nsc.InterpreterLoop.processLine$1(InterpreterLoop.scala:243)
at scala.tools.nsc.InterpreterLoop.repl(InterpreterLoop.scala:249)
at scala.tools.nsc.InterpreterLoop.main(InterpreterLoop.scala:559)
at scala.tools.nsc.MainGenericRunner$.process(MainGenericRunner.scala:75)
at scala.tools.nsc.MainGenericRunner$.main(MainGenericRunner.scala:31)
at scala.tools.nsc.MainGenericRunner.main(MainGenericRunner.scala)
Подведем итог:
- Использовать хвостовые рекурсивные методы
- Аннотировать их как хвост-рекурсивные
- Когда вы вызываете их, убедитесь, что их аргумент является единственной ссылкой на
Stream
EDIT:
Обратите внимание, что это также работает и не приводит к ошибке нехватки памяти:
scala> def s = Stream.range(0, 100000000)
s: scala.collection.immutable.Stream[Int]
scala> last(s)
res1: Int = 99999999
EDIT2:
И в случае reduceLeft
, который вам требуется, вы должны определить вспомогательный метод с аргументом аккумулятора для результата.
Для reduLeft вам нужен аргумент-накопитель, который можно установить на определенное значение, используя аргументы по умолчанию. Упрощенный пример:
scala> @tailrec def rcl(s: Stream[Int], acc: Int = 0): Int = if (s.isEmpty) acc else rcl(s.tail, acc + s.head)
rcl: (s: scala.collection.immutable.Stream[Int],acc: Int)Int
scala> rcl(Stream.range(0, 10000000))
res6: Int = -2014260032