2012-09-18 12 views
10

In der folgenden SituationScala tail-rekursive Stream-Prozessorfunktion in Merkmale definiert hält Referenz

trait T { 

@tailrec 
def consume[A](as: Stream[A]): Unit = { 
    if (as.isEmpty)() 
    else consume(as.tail) 
    } 
} 

object O extends T 

Aufruf O.consume(Range(1, N).toStream) mit N groß genug, um an-Kopf zu strömen, wird das Programm aus dem Speicher ausgeführt werden, oder zumindest verbrauchen O (N) anstelle des benötigten O (1).

+0

Siehe auch http://stackoverflow.com/questions/12529697/how-to-write-non-leaking-tail-recursive-stream-function-in-scala – ron

Antwort

10

Die Tail-rekursive Methode wird für das Merkmal generiert. Der Methodeneintrag im Extender des Merkmals (hier O) leitet den Aufruf an die Methode des Merkmals weiter, behält dabei aber einen Verweis auf den Kopf des Streams.

Die Methode ist also tail-rekursiv, aber der Speicher kann immer noch nicht freigegeben werden. Abhilfe: Definieren Sie Stream-Funktionen nicht in Merkmalen, sondern direkt in Objekten.

Eine Alternative ist die EphemeralStream von scalaz, die schwache Referenzen auf den Stream-Kopf und -Schwanz enthält und diese bei Bedarf neu berechnet.

+0

[EphemeralStream] (https://github.com/scalaz/scalaz/blob/scalaz-seven/core/src/main/scala/scalaz/EphemeralStream.scala) –

2

Es gibt eine einfache Problemumgehung. Wickeln Sie einfach Ihr Schwanz rekursive Strom Verbraucher in einer anderen Funktion, die den Strom über einen by-name Parameter erhält:

import scala.annotation.tailrec 

trait T { 
    def consume[A](as: => Stream[A]): Unit = { 
    @tailrec 
    def loop[A](as: Stream[A]): Unit = { 
     if (as.isEmpty)() 
     else loop(as.tail) 
    } 
    loop(as) 
    } 
} 

object O extends T { 
    def main(args: Array[String]): Unit = 
    O.consume(Range(1, 1000000000).toStream) 
} 

Der Spediteur Verfahren wird eine Referenz auf eine Funktion halten einen Ausdruck der Berechnung des Ergebnisses von dem ein Strom ist :

public final class O$ implements T { 
    public static final MODULE$; 
    // This is the forwarder: 
    public <A> void consume(Function0<Stream<A>> as) { 
    T.class.consume(this, as); 
    } 
    . . .  

    public void main(String[] args) { 
    consume(new AbstractFunction0() { 
     public final Stream<Object> apply() { 
     return package..MODULE$.Range().apply(1, 1000000000).toStream(); 
     } 
    }); 
    } 
} 
Verwandte Themen