13

An infinite stream:

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

How is it possible for a value to be used in its own declaration? It seems this should produce a compiler error, yet it works.

Chris Martin
  • 30,334
  • 10
  • 78
  • 137
axiopisty
  • 4,972
  • 8
  • 44
  • 73

3 Answers3

10

It's not always a recursive definition. This actually works and produces 1:

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

variable a is created when you type val a: Int, so you can use it in the definition. Int is initialized to 0 by default. A class will be null.

As @Chris pointed out, Stream accepts => Stream[A] so a bit another rules are applied, but I wanted to explain general case. The idea is still the same, but the variable is passed by-name, so this makes the computation recursive. Given that it is passed by name, it is executed lazily. Stream computes each element one-by-one, so it calls ones each time it needs next element, resulting in the same element being produces once again. This works:

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

Though you can make infinite stream easier: Stream.continually(1) Update As @SethTisue pointed out in the comments Stream.continually and Stream.cons are two completely different approaches, with very different results, because cons takes A when continually takes =>A, which means that continually recomputes each time the element and stores it in the memory, when cons can avoid storing it n times unless you convert it to the other structure like List. You should use continually only if you need to generate different values. See @SethTisue comment for details and examples.

But notice that you are required to specify the type, the same as with recursive functions.

And you can make the first example recursive:

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

This will stackoverflow.

Archeg
  • 8,364
  • 7
  • 43
  • 90
  • 2
    Just a note, `Stream.cons(1, ones)` is a circular structure that uses finite memory, `Stream.continually(1)` is a linear structure that uses potentially unbounded memory. – Seth Tisue Nov 05 '15 at 04:26
  • 1
    @SethTisue Could you explain? Is there any practical difference? Not sure what do you mean. If you do `Stream.continually(1).take(10000000)` it still won't allocate all those numbers – Archeg Nov 05 '15 at 04:39
  • 2
    There is an enormous practical difference between something that uses only a few words of memory, and something that could consume the entire heap. (Note, however, that once you call `take` you end up with the same structure either way, so whether it matters depends on what you actually do with the stream.) Further reading: https://gist.github.com/SethTisue/ce598578874accba98c0, https://groups.google.com/d/msg/scala-user/3yypUKJBP04/Q_bowgIry44J – Seth Tisue Nov 05 '15 at 12:51
  • 2
    @SethTisue wow! I didn't realize that `cons` takes `A`, when `continually` takes `=>A`. I agree this makes these two methods completely different. – Archeg Nov 05 '15 at 12:57
9

Look at the signature of Stream.cons.apply:

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

The on the second parameter indicates that it has call-by-name semantics. Therefore your expression Stream.cons(1, ones) is not strictly evaluated; the argument ones does not need to be computed prior to being passed as an argument for tl.

Chris Martin
  • 30,334
  • 10
  • 78
  • 137
3

The reason this does not produce a compiler error is because both Stream.cons and Cons are non-strict and lazily evaluate their second parameter.

ones can be used in it's own definition because the object cons has an apply method defined like this:

/** 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)

And Cons is defined like this:

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

Notice that it's second parameter tl is passed by name (=> Stream[A]) rather than by value. In other words, the parameter tl is not evaluated until it is used in the function.

One advantage to using this technique is that you can compose complex expressions that may be only partially evaluated.

axiopisty
  • 4,972
  • 8
  • 44
  • 73