Describing State in FP Scala

Algimantas Krasauskas
Wix Engineering
Published in
3 min readAug 28, 2019
Photo by Patrick Fore on Unsplash

When learning FP, it’s quite common to assume that modelling state mutation is hard. A simple value update in an imperative style can be hard to reason in pure FP code:

In small scope, imperative mutations look appealing, but what if we want to compose them. How would one even approach such a task? In that case, we would leverage our knowledge in functional composition by describing mutation as a function type State[S, A] = S => (S, A). With this simple representation, we can express our program from above like this:

As you can see from the syntax above, it does not look pretty. To improve code readability, we might want to represent it as a for expression.

Let’s useCats library to help us build a Monad definition (if the ? Looks weird you can learn more about it here. Another way to express this is ({type L[A] = State[S, A]})#L:

Now the imperative code above can be expressed as:

Building Pseudo-Random composable Generator

A more interesting example is trying to build a composable generator that mimics the features of ScalaCheck. We can define our Generator State as type Gen[A] = State[Seed, A] , where Seed is:

And some primitive values builders can be expressed as:

When primitives are out of the way we can start building values that have size boundaries:

We can easily compose generators to get more complex generators:

We can now use our generator like this: genString(Seed(20L))

Adding configurations

That’s all good and great, but how do we add some configurations. Let’s image we want to prefix all our string with a certain prefix . To do that we need to adjust our State to support configuration passing. We should change it to:

Let’s redefine our Gen type as well as define how our configuration looks like:

There are some minor places where the code needs to change:

And finally our genString :

We can now use our generator like this: genString(Config(prependToStr = "test-"), Seed(20L))

Adding Logging

The final bit that would make this generator super awesome is some logging mechanism that would let us debug the generating process. As before we need to look into types to express this concern. The vital thing to note is that our logging accumulates. So it must be a Monoid, that means that it describes zero and associative binary operation, that adds logs together:

As previously we need to adjust our Gen definition:

And our genChar and genString definition:

If we call it as before, our output looks a bit different:

Conclusion

Building a stateful application requires an understanding of primitive constructs as state (S => (S, A))for simulating mutation,writer (A => (W, A)) for aggregation of messages, reader (A => B) for adding additional dependencies and configurations to your computation. With these concepts at hand, we can model arbitrary composable programs that express even the most complicated requirements.

References

Hope you liked the post, here are some links to help you understand these concepts even better:

--

--

Algimantas Krasauskas
Wix Engineering

Engineer that strives for excellence and growth. Currently working at @Wix and loving it!