Monday, January 20, 2020

On the Pull with FS2


The learning curve for FS2 is steep and comparing it to Scala's built-in streams is not helpful. Given this Scala stream to generate Fibonacci numbers:

  val f: Stream[Int] = 0  #:: 1  #:: f.zip(f.tail).map { case (x, y) => x + y }

you might think you can do this:

    val spliced = f.take(5) ++ f.drop(5)

and treat spliced as normal:

    println(spliced.take(10).mkString(", ")) // 0, 1, 1, 2, 3, 5, 8, 13, 21, 34

And you'd be correct when working with Scala streams but not for FS2 streams, that is s != s.take(n) ++ s.drop(n)
Fabio Labella @SystemFw
I mean, this is certainly not the case in general, since the RHS reevaluates s twice
That is, s.take(n) ++ s.drop(n) will return the same values as Scala's streams but evaluate different effects to what you might expect.

For example, if we had a stream:

    rangeStream(6).evalMap { x =>
      IO {
        println(s"x = $x")
        x
      }
    }

then s.take(3) ++ s.drop(3) would indeed return a Stream containing 1,2,3,4,5,6  but you'd see this printed out:

x = 1
x = 2
x = 3
x = 1
x = 2
x = 3
x = 4
x = 5
x = 6

As Fabio says "you can use Pull if you want 'take some, then give me the rest' semantics". So, this is what I did here on GitHub.

Aside: the reason I want to splice an FS2 Stream is to introduce some test assertions. Here, I want the opposite of the above, namely to evaluate the effect (my assertion in this case) but to ignore the value. Here, one can use

Stream.eval_
 
Note the underscore. However, be a bit careful here as if there is no value returned, calls like myStream.take may semantically block.

No comments:

Post a Comment