Effects all the way down
17 Sep 2019I was recently writing some code using Scala’s fs2 streaming library, where I wanted
to produce a stream of effects, starting with a given effect and then generating
each subsequent effect from the value produced by the preceding effect. Unless I missed something,
fs2 doesn’t seem to have this facility built in; there’s iterateEval
, which
produces a stream of effects, but this requires a seed value which is itself
not produced by an effect. It turns out to be fairly straight-forward to write
the function I wanted; I’m posting it here in the hope it will be useful for anyone
facing the same problem I did (as always, that ‘someone’ could well be future me).
def iterateEvalFromEffect[F[_], A](start: F[A])(f: A => F[A])
: Stream[F, A] =
Stream.eval(start)
.flatMap(s => Stream.emit(s) ++
iterateEvalFromEffect(f(s))(f))
This also turned out to be an interesting example of how types can help guide development.
I began by looking at the source for iterateEval
, which is:
def iterateEval[F[_], A](start: A)(f: A => F[A]): Stream[F, A] =
emit(start) ++ eval(f(start)).flatMap(iterateEval(_)(f))
My initial thought was that I could just change the start
parameter be an effect (of
type F[A]
), and replace the first call to emit
with one to eval
. But it
became obvious when I looked at the recursive call that that wouldn’t work, because
the function passed to flatMap
has to take an A
, not an F[A]
. So I started
thinking about how I would write the function so that I would have access to
an A
when I needed one to emit, and an F[A]
when I needed one to eval. Once I
started thinking about that, it didn’t take long to come up with the function I
needed. Of course, I could have undertaken the same thought process without types,
but I think the type system giving me clear labels for the different sorts of
thing I was dealing with, and forcing me to think about what kind of thing I
needed at each point in the algorithm, added some really helpful clarity to the
development process.