11

Un flusso infinito:Una variabile utilizzata nella propria definizione?

val ones: Stream[Int] = Stream.cons(1, ones)

Come è possibile per un valore da utilizzare nella propria dichiarazione? Sembra che questo dovrebbe produrre un errore del compilatore, eppure funziona.

+2

Allo stesso modo funzionano le funzioni ricorsive. Pensare a "ones" è una funzione a zero argomenti. –

risposta

8

Non è sempre una definizione ricorsiva. Questo in realtà funziona e produce 1:

val a : Int = a + 1 
println(a) 

variabile a si crea quando si digita val a: Int, in modo da poter utilizzare nella definizione. Int viene inizializzato su 0 per impostazione predefinita. Una classe sarà nullo.

Come sottolineato da @Chris, Stream accetta => Stream[A] quindi vengono applicate altre regole, ma volevo spiegare il caso generale. L'idea è sempre la stessa, ma la variabile viene passata per nome, quindi rende ricorsivo il calcolo. Dato che è passato per nome, viene eseguito pigramente. Stream calcola ogni elemento uno per uno, quindi chiama ones ogni volta che ha bisogno dell'elemento successivo, in modo che lo stesso elemento venga prodotto ancora una volta. Questo funziona:

val ones: Stream[Int] = Stream.cons(1, ones) 
println((ones take 10).toList) // List(1, 1, 1, 1, 1, 1, 1, 1, 1, 1) 

Anche se si può fare flusso infinito facile: Stream.continually(1)Aggiornamento Come @SethTisue sottolineato nei commenti Stream.continually e Stream.cons sono due approcci completamente diversi, con risultati molto diversi, perché cons prende A quando continually prende =>A, il che significa che lo strumento continually si ricalcola ogni volta che viene memorizzato e che l'elemento lo memorizza, quando lo cons può evitare di memorizzarlo n volte a meno che non lo si converta nell'altra struttura come List. È necessario utilizzare continually solo se è necessario generare valori diversi. Vedi il commento @SethTisue per dettagli ed esempi.

Tuttavia, è necessario specificare il tipo, come per le funzioni ricorsive.

E si può fare il primo esempio ricorsivo:

lazy val b: Int = b + 1 
println(b) 

Questo StackOverflow.

+2

Solo una nota, 'Stream.cons (1, ones)' è una struttura circolare che utilizza una memoria finita, 'Stream.continually (1)' è una struttura lineare che utilizza una memoria potenzialmente illimitata. –

+1

@SethTisue Potresti spiegare? C'è qualche differenza pratica? Non sei sicuro di cosa intendi. Se esegui 'Stream.continually (1) .take (10000000)' non assegnerà ancora tutti quei numeri – Archeg

+2

C'è un'enorme differenza pratica tra qualcosa che usa solo poche parole di memoria e qualcosa che potrebbe consumare l'intero mucchio. (Nota, tuttavia, che una volta che chiami 'take' ti ritrovi con la stessa struttura in entrambi i casi, quindi se importa dipende da cosa fai effettivamente con lo stream.) Ulteriori letture: https://gist.github.com/ SethTisue/ce598578874accba98c0, https://groups.google.com/d/msg/scala-user/3yypUKJBP04/Q_bowgIry44J –

9

Guardate la firma del Stream.cons.apply:

apply[A](hd: A, tl: ⇒ Stream[A]): Cons[A]

La sul secondo parametro indica che ha chiamata per nome semantica. Pertanto la tua espressione Stream.cons(1, ones) non viene valutata rigorosamente; l'argomento ones non deve essere calcolato prima di essere passato come argomento per tl.

3

Il motivo per cui questo non produce un errore di compilazione è perché sia ​​Stream.cons e Cons sono non-strict e lazily evaluate loro secondo parametro.

ones può essere utilizzato in essa la propria definizione, perché il cons oggetto ha un metodo applicabile definita in questo modo:

/** A stream consisting of a given first element and remaining elements 
* @param hd The first element of the result stream 
* @param tl The remaining elements of the result stream 
*/ 
def apply[A](hd: A, tl: => Stream[A]) = new Cons(hd, tl) 

ei contro si definisce in questo modo:

final class Cons[+A](hd: A, tl: => Stream[A]) extends Stream[A] 

Avviso che è secondo parametro tl viene passato per nome (=> Stream[A]) anziché per valore. In altre parole, il parametro tl non viene valutato finché non viene utilizzato nella funzione.

Un vantaggio dell'utilizzo di questa tecnica è che è possibile comporre espressioni complesse che possono essere valutate solo parzialmente.