Extreme branchless: Cupcake
Continuing my branchless journey.
Today, the Cupcake kata.
This kata aims to introduce OOP's Design Pattern Decorator.
In functional programming, it'll be translated into nesting (functions or types/constructors).
We'll use functions in order to be branchless.
Let's import the test cases from the kata description:
spec =
describe "Cupcake" $ do
describe "price" $
forM_ testCases $ \(name, cake, _, price') ->
it name $
cake.price `shouldBe` price'
describe "price" $
forM_ testCases $ \(name, cake, cakeName', _) ->
it name $
cakeName cake `shouldBe` cakeName'
testCases =
[ ("cupcake", cupcake, "π§", 100)
, ("cookie", cookie, "πͺ", 200)
, ("cupcake with chocolate", chocolate cupcake, "π§ with π«", 110)
, ("cookie with chocolate and nuts", nuts $ chocolate cookie, "πͺ with π« and π₯", 230)
, ("cookie with nuts and chocolate", chocolate $ nuts cookie, "πͺ with π₯ and π«", 230)
, ("cookie with nuts", nuts cookie, "πͺ with π₯", 220)
]
Note: we reuse some types we have defined in previous logs:
newtype USDAmount = USDAmount {unUSDAmount :: Int}
deriving newtype (Eq, Ord, Num)
show (USDAmount x) = "$" <> printf "%.2f" (fromIntegral @_ @Float x / 100)
newtype List a = List (forall b. b -> (a -> List a -> b) -> b)
runList e ne (List f) = f e ne
type Item (List a) = a
fromList =
\case
[] -> List const
(x : xs) -> List $ \_ t -> t x (fromList xs)
toList (List f) = f [] (\h t -> h : toList t)
List f <> List g = List $ \h t -> f (g h t) (\h' t' -> t h' (t' <> List g))
mempty = List const
We have two functions:
price
which is a simple number, it can be freely accumulatedcakeName
which as several components:- A cake "main" name
- Followed by
"with topping0 [and topping1 ...]"
when there are topping:- Either we build a
String
directly, which would force us to either inspect it, or have a state, or we simply keep a list
- Either we build a
data Cake = Cake
{ symbol :: String
, price :: USDAmount
, toppings :: List String
}
First thing first, let's define the base cakes:
cookie =
Cake {symbol="πͺ", price=200, toppings=mempty}
cupcake =
Cake {symbol="π§", price=100, toppings=mempty}
Then we can have a type for toppings, which wraps Cake
:
type Topping = Cake -> Cake
All Topping
s are identical: they increment the price
and add a topping symbol:
mkTopping symbol' price' cake = cake { price = cake.price + price', toppings = cake.toppings <> [symbol']}
We can then define our toppings as:
chocolate = mkTopping "π«" 10
nuts = mkTopping "π₯" 20
And finally, how cakeName
is computed:
cakeName cake = cake.symbol <> toppingsName
where toppingsName = runList "" (\h t -> " with " <> h <> multipleToppings t) cake.toppings
multipleToppings = runList "" (\h t -> " and " <> h <> multipleToppings t)
And, that's it, happy tasting!