In Scala there is a Stream class that is very much like an iterator. The topic Difference between Iterator and Stream in Scala? offers some insights into the similarities and differences between the two.
Seeing how to use a stream is pretty simple but I don't have very many common use-cases where I would use a stream instead of other artifacts.
The ideas I have right now:
So have I missed any big uses? Or is it a developer preference for the most part?
Thanks
The Stream is a lazy lists where elements are evaluated only when they are needed. This is a scala feature. Scala supports lazy computation. It increases performance of our program. Streams have the same performance characteristics as lists.
Unlike operations directly on a concrete collection like List , operations on Iterator are lazy. A lazy operation does not immediately compute all of its results.
The main difference between a Stream
and an Iterator
is that the latter is mutable and "one-shot", so to speak, while the former is not. Iterator
has a better memory footprint than Stream
, but the fact that it is mutable can be inconvenient.
Take this classic prime number generator, for instance:
def primeStream(s: Stream[Int]): Stream[Int] = Stream.cons(s.head, primeStream(s.tail filter { _ % s.head != 0 })) val primes = primeStream(Stream.from(2))
It can be easily be written with an Iterator
as well, but an Iterator
won't keep the primes computed so far.
So, one important aspect of a Stream
is that you can pass it to other functions without having it duplicated first, or having to generate it again and again.
As for expensive computations/infinite lists, these things can be done with Iterator
as well. Infinite lists are actually quite useful -- you just don't know it because you didn't have it, so you have seen algorithms that are more complex than strictly necessary just to deal with enforced finite sizes.
In addition to Daniel's answer, keep in mind that Stream
is useful for short-circuiting evaluations. For example, suppose I have a huge set of functions that take String
and return Option[String]
, and I want to keep executing them until one of them works:
val stringOps = List( (s:String) => if (s.length>10) Some(s.length.toString) else None , (s:String) => if (s.length==0) Some("empty") else None , (s:String) => if (s.indexOf(" ")>=0) Some(s.trim) else None );
Well, I certainly don't want to execute the entire list, and there isn't any handy method on List
that says, "treat these as functions and execute them until one of them returns something other than None
". What to do? Perhaps this:
def transform(input: String, ops: List[String=>Option[String]]) = { ops.toStream.map( _(input) ).find(_ isDefined).getOrElse(None) }
This takes a list and treats it as a Stream
(which doesn't actually evaluate anything), then defines a new Stream
that is a result of applying the functions (but that doesn't evaluate anything either yet), then searches for the first one which is defined--and here, magically, it looks back and realizes it has to apply the map, and get the right data from the original list--and then unwraps it from Option[Option[String]]
to Option[String]
using getOrElse
.
Here's an example:
scala> transform("This is a really long string",stringOps) res0: Option[String] = Some(28) scala> transform("",stringOps) res1: Option[String] = Some(empty) scala> transform(" hi ",stringOps) res2: Option[String] = Some(hi) scala> transform("no-match",stringOps) res3: Option[String] = None
But does it work? If we put a println
into our functions so we can tell if they're called, we get
val stringOps = List( (s:String) => {println("1"); if (s.length>10) Some(s.length.toString) else None }, (s:String) => {println("2"); if (s.length==0) Some("empty") else None }, (s:String) => {println("3"); if (s.indexOf(" ")>=0) Some(s.trim) else None } ); // (transform is the same) scala> transform("This is a really long string",stringOps) 1 res0: Option[String] = Some(28) scala> transform("no-match",stringOps) 1 2 3 res1: Option[String] = None
(This is with Scala 2.8; 2.7's implementation will sometimes overshoot by one, unfortunately. And note that you do accumulate a long list of None
as your failures accrue, but presumably this is inexpensive compared to your true computation here.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With