Extreme branchless: Gilded Rose Part 2
Continuing my attempt of the Gilded Rose.
We can start to inline and simplify mkBaseCase
:
agedBrie sellIn quality =
Item'
{ _name = "Aged Brie",
_sellIn = sellIn,
_quality = quality,
updateQuality =
agedBrie (sellIn - 1) . min 50 $
if sellIn < 1
then quality + 2
else quality + 1
}
backstagePasses sellIn quality =
Item'
{ _name = "Backstage passes to a TAFKAL80ETC concert",
_sellIn = sellIn,
_quality = quality,
updateQuality =
let
quality'
| sellIn < 6 = min 50 (quality + 3)
| sellIn < 11 = min 50 (quality + 2)
| otherwise = min 50 (quality + 1)
in
backstagePasses (sellIn - 1) $
if sellIn < 1
then 0
else quality'
}
sulfuras sellIn quality =
Item'
{ _name = "Sulfuras, Hand of Ragnaros",
_sellIn = sellIn,
_quality = quality,
updateQuality = sulfuras sellIn quality
}
baseCase name sellIn quality =
Item'
{ _name = name,
_sellIn = sellIn,
_quality = quality,
updateQuality =
baseCase name (sellIn - 1) . max 0 $
if quality > 1 && sellIn < 1
then quality - 2
else quality - 1
}
Great so far, each case is well-defined and has no dependency.
Our next step is to get rid of if
, we can rely on (infinite) lists to map values:
agedBrie sellIn quality =
Item'
{ _name = "Aged Brie",
_sellIn = sellIn,
_quality = quality,
updateQuality =
agedBrie (sellIn - 1) . min 50 $
((quality + 2) : repeat (quality + 1)) `at` sellIn
}
Note: at
is like (!!)
, except that it does not throw when the index is negative.
Here we build a list corresponding to the if
statement:
quality + 2
whensellIn == 0
(sellIn < 1
), there's no difference in this designquality + 1
otherwise
The next one works in the same way:
backstagePasses sellIn quality =
Item'
{ _name = "Backstage passes to a TAFKAL80ETC concert",
_sellIn = sellIn,
_quality = quality,
updateQuality =
let
quality' =
min 50 $
(replicate 6 (quality + 3) <> replicate 5 (quality + 2) <> repeat (quality + 1)) `at` sellIn
in
backstagePasses (sellIn - 1) $
(0 : repeat quality') `at` sellIn
}
The next one is a bit trickier since it is based on two properties
sellIn
and quality
.
We can rely on shortcuts with Maybe a
, for instance, it comes with many
functions, such as (<*)
which works as follows:
Nothing <* Nothing == Nothing
Just 1 <* Nothing == Nothing
Nothing <* Just 1 == Nothing
Just 1 <* Just () == Just 1
So, we have to design a value space for each property and join them:
baseCase name sellIn quality =
Item'
{ _name = name,
_sellIn = sellIn,
_quality = quality,
updateQuality =
baseCase name (sellIn - 1) . max 0 . fromMaybe (quality - 1) $
(Just (quality - 2) : repeat Nothing) `at` sellIn <* (Nothing : Nothing : repeat (Just ())) `at` quality
}
We have a value when sellIn == 0 && quality >= 2
, we index each property
on its value space, join them with (<*)
, if they have both a Just _
, it
means their conditions are met, and we return quality + 1
, or we default on quality - 1
.
And that's it, a pure branchless Gilded Rose.