Reactive Sous Vide Cooking
Table of Contents
Sous Vide Is the Combine of Cooking (and It’s Not Just a Joke)#
Have you ever moved from imperative to reactive, functional Combine code and thought:
“Wait… why doesn’t everything work like this?”
That’s exactly how I felt after discovering sous vide cooking.
Sous vide isn’t just a trendy food technique. It’s a declarative, functional, reactive system for cooking. It replaces the chaos of heat guessing and overcooked edges with precise timing, temperature control, and fully composed flavour pipelines.
And yes — it’s basically Combine, but for food.
Traditional Cooking Is Imperative#
Most traditional cooking is full of guesswork, manual state management, and side effects. You:
- Flip meat constantly
- Poke it to check doneness
- Guess when it’s ready
- Hope the inside isn’t raw while the outside burns
That’s imperative cooking — direct, stateful, and prone to edge-case disasters.
var isDone = false
while !isDone {
checkCenter()
flipSteak()
hopeItWorks()
}
Sous Vide Is Combine#
Now let’s look at sous vide — where you:
- Set the desired temperature (like defining a
Publisher
) - Apply a precise duration (like chaining
.debounce(for:)
) - Add aromatics and fats (like injecting dependencies)
- Finish with a sear (a side-effect terminal operator)
Suddenly, your food becomes a purely defined stream of transformation:
ribeyePublisher
.setTemperature(54)
.forDuration(.hours(2))
.combineLatest(thyme)
.map { sear($0) }
.sink { plate($0) }
That’s not just metaphor — that’s real cooking logic.
Ingredients Are Data — The Bag Is the DI Container#
Before your food enters the pipeline, it’s prepared with all its required dependencies:
- A protein with defined attributes: cut, fat content, target temp
- Aromatics like garlic or thyme
- Optional modifiers like butter, ghee, miso, or oil
You seal this together in a sous vide bag — your immutable dependency container:
let protein = Protein(cut: .ribeye, temp: 54)
let flavor = [thyme, garlic, butter]
let bag = SousVidePackage(main: protein, additions: flavor)
The bag acts like a sealed struct — containing everything your food needs to be transformed with zero mutation mid-process.
Once it enters the bath, it’s a pure, self-contained unit of flavour.
Cooking Pipelines: Combine-Style Breakdown#
Combine Operator | Cooking Equivalent |
---|---|
.setTemperature(_:) |
Declare target doneness |
.forDuration(_:) |
Sous vide cooking time |
.combineLatest |
Add infused flavours (herbs, fats) |
.map |
Sear, smoke, or torch the result |
.delay(for:) |
Resting time |
.sink |
Serve the final plate |
Sous vide lets you build entire meals this way. Want to run proteins and veggies at different temps? Just spin up two pipelines (containers + circulators), and compose your plate from multiple streams.
Why This Actually Matters#
This isn’t just a metaphor — it’s functionally better cooking.
Most of the cooking process becomes purely declarative:
you describe the end state (target temperature, duration, flavour infusions), and the sous vide system ensures it reaches that state with precision.
And yes, we still use imperative cooking — but only at the end.
The sear, the smoke, the torch — these are controlled, purposeful side effects, isolated to a small, well-defined step. That step is short, visible, and easy to get right.
This separation of concerns means:
- The majority of the cooking is error-free and repeatable
- The only mutable phase is optional and cosmetic (crust, browning)
- You dramatically minimise the risk of failure, because 95% of the process is handled declaratively
Just like a good software architecture, sous vide isolates the risky, stateful parts of the process, keeping them out of the core logic — and your food is better for it.
Structuring Your Meal as Data#
With sous vide, you start thinking in food components:
struct Protein {
let cut: Cut
let weight: Grams
let desiredTemp: Celsius
let aromatics: [Herb]
}
Even flavour additions can be typed and quantified:
protocol Flavoring {
associatedtype Unit
var amount: Unit { get }
}
struct Salt: Flavoring {
typealias Unit = Grams
let amount: Grams
}
struct Thyme: Flavoring {
typealias Unit = Sprigs
let amount: Sprigs
}
Each ingredient can now express what it is and how much of it belongs in the bag, keeping your recipe data clean, predictable, and reusable.
The more you model your meals like this, the more you realise:
you’re not improvising — you’re declaring data, composing systems, and defining a pipeline that transforms raw input into delicious output.
Conclusion#
Sous vide didn’t just improve my meals — it changed the way I think about food.
It’s precise. It’s elegant. It’s reactive.
It respects the ingredient, avoids mutation, and lets me compose my meals with confidence.
And perhaps most beautifully: with sous vide, you start the pipeline, walk away, and wait for the system to emit a ready-to-finish result — perfectly aligned with Combine’s flow.
You don’t poke or poll. You don’t micromanage.
You subscribe once, and the water does the rest.
All you need to do… is sink { searAndServe($0) }
.
Combine your flavours.
Compose your meals.
Serve with intention.
You’re not just cooking.
You’re declaring dinner.
PS: No sink
was harmed in the making of this ribeye.