When most people think of streaming problems, they think of HTTP servers, event queues, or terabytes of data. Personally, I think of cooking simulations.
Perhaps it’s because of nostalgia. The first time I encountered streams was way back when making an engine for a cooking game. I hunted for ideas on how to describe the temporal process of dicing an onion in a way that could hook into a render loop; and, after fiddling a bit with functional programming, stumbled upon fs2.
It lent itself to the problem perfectly. It had a simple model for describing processes — the Stream — a handful of functions for transforming them — termed pipes — and gave me superpowers. Because unlike other streaming frameworks, it didn’t constrain me to the transformations that came with it. If a pipe didn’t exist, I could roll up my sleeves and build one myself using fs2’s plumbing, in exactly the same way as all of fs2’s pipes were built.
fs2’s has matured over the years. There’s a rich selection of pipes for doing almost anything you’d ever want to do with a stream. And for those niche cases that it doesn’t quite cater for, you can still cater to yourself.
We’re about to step into the kitchen of fs2. We’ll learn to use its pull to cook up our own custom pipes. Once done, you’ll be able to whip up a pipe for every problem, and you’ll get an idea of how fs2’s own pipes are made too.
And, for nostalgia, we’ll do so with a cooking simulation.
We’re going to simulate the cooking of one of my cravings: jiaozi.
Jiaozi, for those that haven’t yet had the pleasure of eating them, are Chinese dumplings. They consist of a thin skin of dough usually filled with pork and cabbage (but chicken, prawn or mushroom all make decent fillings) and delicately folded. They can be cooked many ways, but personally I prefer to boil them and douse them in soy sauce and chili oil, after which they become delicious warming packages.
Making jiaozi is a laborious multi-stage process. The dough should be made from scratch, then rolled into logs, cut into larger-than-your-thumb-sized pieces and flattened into discs. These are then stuffed with finely minced filling and intricately folded into bulging purse-like packets. They are then boiled under a watchful eye, as a batch of jiaozi is cooked through in a matter of minutes. Finally, the most satisfying part of the process is to serve and eat them while still steaming hot. Any leftovers can be stored and fried the next morning to make a variation of the classic potstickers.
Since there are so many stages that can be done concurrently, jiaozi are usually made as a group with friends and family. While one person boils a batch, another can fold the skins. An efficient group of cooks can pump out hundreds of jiaozi per hour.
For similar reasons, they’re also a perfect topic to explore streams with.
We’re going to simulate a jiaozi production line by simplifying it into four stages:
Rolling. The dough is mixed and rolled into neat little logs.
Cooking. This involves cutting the logs into pieces, flattening them into discs, filling them until bulging with deliciousness and finally boiling them into soft, juicy packages. In a real kitchen, these would be several stages, each manned by a competent cook, but we’re simplifying things.
Serving. The easiest job is to heap up enough steaming jiaozi as you can possibly wolf down.
Storing. Any leftovers are boxed and stored in the fridge to for next morning’s potstickers.
Assuming you’re familiar with SBT, create a
build.sbt file with the following contents.
ThisBuild / scalaVersion := "3.2.0" ThisBuild / libraryDependencies += "co.fs2" %% "fs2-core" % "3.7.0" ThisBuild / initialCommands := s""" import fs2._, cats.effect._, cats.effect.unsafe.implicits.global """
Then enter the console with