Playing with streams

304 views
Skip to first unread message

wwagner4

unread,
Aug 24, 2012, 9:30:52 AM8/24/12
to scala-...@googlegroups.com
object StreamsTryout extends App {

// What basic function exist to create a Stream
{
val s = Stream.from(-1)
println("(1)" + take10(s))
}
{
val s = Stream.from(100, -3)
println("(2)" + take10(s))
}
{
val s = Stream.continually("A")
println("(3)" + take10(s))
}
{
val s = "B" #:: Stream.continually("A")
println("(4)" + take10(s))
}
// Add a separator except for the first item using the [false, true, true, true, ...] stream s1
{
val s1 = false #:: Stream.continually(true)
val s2 = List("A", "B", "C")
val s3 = s2 zip s1
val l = (s3 map { case (v, b) => if (b) List("|", v) else List(v) }).flatten mkString ("(", "", ")")
println("(5a)" + l)
}
// The [false, true, true, true, ...] stream s1 can be reused in different threads at the same time. It is immutable
{
val s1 = false #:: Stream.continually(true)
scala.actors.Actor.actor {
val s2 = 1 to 5
val s3 = s2 zip s1
val l = (s3 map { case (v, b) => if (b) List("|", v) else List(v) }).flatten mkString ("(", "", ")")
println("(5b)" + l)
}
scala.actors.Actor.actor {
val s2 = List("A", "B", "C")
val s3 = s2 zip s1
val l = (s3 map { case (v, b) => if (b) List("|", v) else List(v) }).flatten mkString ("(", "", ")")
println("(5c)" + l)
}
scala.actors.Actor.actor {
val s2 = 1 to 200
val s3 = s2 zip s1
val l = (s3 map {case (v, true) => List("|", v) case (v, _) => List(v) }).flatten mkString ("(", "", ")")
println("(5d)" + l)
}
scala.actors.Actor.actor {
val s2 = 1 to 200
val s3 = s2 zip s1
val l = (s3 map {case (v, true) => List("|", v) case (v, _) => List(v)}).flatten mkString ("(", "", ")")
println("(5e)" + l)
}
}
{
// How can that be written in one line ?
val s1: Stream[Option[String]] = Stream.continually(Some("A"))
val s = None #:: s1
println("(6)" + take10(s))
}
// Iterate is for me the most interesting method of creating streams
{
val s = Stream.iterate(2) { n => n + 3 }
println("(7)" + take10(s))
}
{
val s = Stream.iterate(List.fill(4)(100)) { n => n map (_ + 1) }
println("(8)" + take10(s))
}
{
val s = Stream.iterate(List.fill(4)(-4)) { n => n map (_ + 1) }.flatten
println("(9)" + take10(s))
}
{
val s = Stream.continually(0 until 4)
println("(10)" + take10(s))
}
{
val s = Stream.continually(0 until 4).flatten
println("(11)" + take10(s))
}
// Coordinates for an n columns array produced with two streams s1, s2
{
val n = 3
val s1 = Stream.continually(0 until n).flatten
val s2 = Stream.iterate(List.fill(n)(0)) { n => n map (_ + 1) }.flatten
val s = s1 zip s2
println("(12a)" + take30(s1))
println("(12b)" + take30(s2))
println("(12c)" + take30(s))
println("(12d)" + s(10))
println("(12e)" + s(1000000))
// Much faster than the one before
println("(12f)" + s(999999))
}

def take10[A](s: Iterable[A]) = s.take(10).toList
def take30[A](s: Iterable[A]) = s.take(30).toList

}

Alexander Daniel

unread,
Aug 26, 2012, 8:51:51 AM8/26/12
to scala-...@googlegroups.com
Thanks for sharing! I learned a lot about the methods available for creating streams.

> {
> // How can that be written in one line ?
> val s1: Stream[Option[String]] = Stream.continually(Some("A"))
> val s = None #:: s1
> println("(6)" + take10(s))
> }

This puzzled me! Short answer to the questions:

val s = Stream.cons(None, Stream.continually(Some("A")))

O.k. But why does the version with #:: not work? Let's look at the compiler message:

scala> val s = None #:: Stream.continually(Some("A"))
<console>:7: error: type mismatch;
found : None.type (with underlying type object None)
required: Some[java.lang.String]
val s = None #:: Stream.continually(Some("A"))
^

Hmmmm. The compiler wants a object of type Some[String] and does not accept None. Following code is fine for the compiler:

scala> val s = Some("B") #:: Stream.continually(Some("A"))
s: scala.collection.immutable.Stream[Some[java.lang.String]] = Stream(Some(B), ?)

scala> val s = None #:: Stream.continually(None)
s: scala.collection.immutable.Stream[None.type] = Stream(None, ?)

What is going on? If a method is used in operator notation and the method name ends in a colon, the method is invoked on the right operand, i.e. in we have

scala> val s = Stream.continually(Some("A")).#::(None)
<console>:7: error: type mismatch;
found : None.type (with underlying type object None)
required: Some[java.lang.String]
val s = Stream.continually(Some("A")).#::(None)
^

Of course it gives the same compiler message as before. So where is #:: defined?

It is defined in ConsWrapper in Stream.scala with an implicit conversion:

/** A wrapper class that adds `#::` for cons and `#:::` for concat as operations
* to streams.
*/
class ConsWrapper[A](tl: => Stream[A]) {
def #::(hd: A): Stream[A] = cons(hd, tl)
def #:::(prefix: Stream[A]): Stream[A] = prefix append tl
}

/** A wrapper method that adds `#::` for cons and `#::: for concat as operations
* to streams.
*/
implicit def consWrapper[A](stream: => Stream[A]): ConsWrapper[A] =
new ConsWrapper[A](stream)

I.e. we have

scala> val s = new scala.collection.immutable.Stream.ConsWrapper(Stream.continually(Some("A"))).#::(None)
<console>:7: error: type mismatch;
found : None.type (with underlying type object None)
required: Some[java.lang.String]
val s = new scala.collection.immutable.Stream.ConsWrapper(Stream.continually(Some("A"))).#::(None)
^

and are getting the same compiler message again. ConsWrapper is very strict about the types. They have to match. But we can specify the type when creating ConsWrapper:

scala> val s = new scala.collection.immutable.Stream.ConsWrapper[Option[String]](Stream.continually(Some("A"))).#::(None)
s: scala.collection.immutable.Stream[Option[String]] = Stream(None, ?)

:-) That works. Unwinding again using the implicit conversion and specifying the type parameter:

scala> val s = Stream.continually[Option[String]](Some("A")).#::(None)
s: scala.collection.immutable.Stream[Option[String]] = Stream(None, ?)

:-) Now to operator notation:

scala> val s = None #:: Stream.continually[Option[String]](Some("A"))
s: scala.collection.immutable.Stream[Option[String]] = Stream(None, ?)

:-) Now we have an alternative one line notation but it looks more complicated than the first solution Stream.cons(None, Stream.continually(Some("A")))

So what is the signature of Stream.cons?

/** An alternative way of building and matching Streams using Stream.cons(hd, tl).
*/
object cons {

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

/** Maps a stream to its head and tail */
def unapply[A](xs: Stream[A]): Option[(A, Stream[A])] = #::.unapply(xs)
}

Because we now have one method call with two parameters the compiler can figure out the supertype Option of None and Some and it works.

But why does it work for lists:

scala> val l = None :: List(Some("A"))
l: List[Option[java.lang.String]] = List(None, Some(A))

That I will explore another time.

Alex




Reply all
Reply to author
Forward
0 new messages