Imagine you're a baker. You bake different cakes using different recipes.
You're trying to plan how many ingredients you'll need over the next 6m using your upcoming list of orders.
The challenge is, your orders aren't certain. They're based on a probability.
I've started hallo – a library to run these kind of simulations. I'm stuck on how to implement the ergonomics of the API I've imagined. Here's what I have in mind:
struct Sugar(f32);
struct Chocolate(f32);
struct Vanilla(f32);
let brownieRecipe = RecipeBuilder::default()
.add(Sugar(200.0))
.add(Chocolate(10.0))
.build();
let vanillaSponge = RecipeBuilder::default()
.add(Sugar(300.0))
.add(Vanilla(2.0))
.build();
let p1 = AllocationBuilder::default()
.recipe(brownieRecipe)
.duration_weeks(5)
.start_date(&(today + Duration::weeks(8)))
.build();
let p2 = AllocationBuilder::default()
.recipe(vanillaSponge)
.duration_weeks(2)
.start_date(&(today + Duration::weeks(2)))
.build();
The RecipeBuilder
builds a recipe from any number of ingredients. The AllocationBuilder
takes a recipe and plans when it's needed.
The next step is to implement a simulation engine to plot 10k outcomes (but that's out of scope of this question).
The ingredient structs might eventually implement some trait so that they can be summed (std::Add
?).
You'll need to have some trait for using the ingredients. What trait - that depends on what you need to do with them. It can be a builtin trait or a custom trait, but it is important that all ingredients (Sugar
, Chocolate
etc.) will implement it (I'd go with a custom trait).
Assuming you have a trait Ingredient
, store a list of Vec<Box<dyn Ingredient>>
. For the API to be what you described, you need the add()
method to be defined like:
pub fn add<I: Ingredient>(&mut self, ingredient: I) -> &mut Self {
self.ingredients.push(Box::new(ingredient));
self
}
This is a convenience method: instead of needing to pass Box<dyn Ingredient>
, you pass impl Ingredient
and the method creates the Box<dyn Ingredient>
. This is a very common pattern in Rust.