Kata: Panini

Few days ago, with the Software Crafters Lyon, we have tackled the Nearest color.

The main idea is to represent a few elements of a panini, a set of ingredients (tomatoes, onion, and meat) between two pieces of bread.

This kata aims to practice monoid-based design, finding a neutral element, a composition function and keeping invariants.

Let's start by what it is not, a panini is not a monoid, when we add two panini either we have a set (or a list of panini), or we we take the ingredients from one, throw the bread, and put them in another one.

From the description, we can define nutrition facts as follows:

newtype Fat
  = Fat Natural
  deriving newtype (Eq, Ord, Num, Show)
  deriving (Semigroup, Monoid) via (Sum Fat)

newtype Salt
  = Salt Natural
  deriving newtype (Eq, Ord, Num, Show)
  deriving (Semigroup, Monoid) via (Sum Salt)

newtype Calories
  = Calories Natural
  deriving newtype (Eq, Ord, Num, Show)
  deriving (Semigroup, Monoid) via (Sum Calories)

Note: deriving via is a way to reuse instances from other types, here Sum uses Num to define mempty = 0 and (<>) = (+).The next elements are union types and are defined as follows:

data Diet
  = Pescetarian
  | Vegetarian
  | Vegan
  deriving stock (Eq, Ord, Show, Enum, Bounded, Generic)
  deriving (Semigroup, Monoid) via (Min Diet)

data Organic
  = NonOrganic
  | Organic
  deriving stock (Eq, Ord, Show, Enum, Bounded, Generic)
  deriving (Semigroup, Monoid) via (Min Organic)

_Edit: fixed Organic/Diet constructor orders to match Min semantics._Here we use Min which relies on Ord and Bounded instead.

Finally, we can pack everything together in a Stats type, as follows:

data Stats = Stats
  { diet :: Diet,
    organic :: Organic,
    fat :: Fat,
    salt :: Salt,
    calories :: Calories
  }
  deriving stock (Eq, Show, Generic)
  deriving (Semigroup, Monoid) via (Generically Stats)

Generically going field by field to define the instance.

It can later be used by mconcat.

In summary, this code kata is not a good fit for practicing TDD in Haskell, as it is pure domain modeling.