I came across a nice post on SO by @amalloy while looking for hylomorhism examples, that illustrate recursion scheme (RS) usage with useful discussion and full implementation:
{-# LANGUAGE DeriveFunctor #-}
import Control.Arrow ( (>>>), (<<<) )
newtype Term f = In {out :: f (Term f)}
type Algebra f a = f a -> a
type Coalgebra f a = a -> f a
cata :: (Functor f) => Algebra f a -> Term f -> a
cata fn = out >>> fmap (cata fn) >>> fn
ana :: (Functor f) => Coalgebra f a -> a -> Term f
ana f = In <<< fmap (ana f) <<< f
hylo :: Functor f => Algebra f b -> Coalgebra f a -> a -> b
hylo alg coalg = ana coalg >>> cata alg
data ChangePuzzle a = Solved Cent
| Pending {spend, forget :: a}
deriving Functor
type Cent = Int
type ChangePuzzleArgs = ([Cent], Cent)
coins :: [Cent]
coins = [50, 25, 10, 5, 1]
divide :: Coalgebra ChangePuzzle ChangePuzzleArgs
divide (_, 0) = Solved 1
divide ([], _) = Solved 0
divide (coins@(x:xs), n) | n < 0 = Solved 0
| otherwise = Pending (coins, n - x) (xs, n)
conquer :: Algebra ChangePuzzle Cent
conquer (Solved n) = n
conquer (Pending a b) = a + b
waysToMakeChange :: ChangePuzzleArgs -> Int
waysToMakeChange = hylo conquer divide
The code works as expected. Despite having some vague intuition for the RS aspect already, I am still wondering:
- since this is about counting combinations, why
Solved Centand notSolved Int? (This may sound like a nitpic, if it is even a reasonable question, but I am hoping it may be the root of the rest of the uncertainty, below, although I suspect I missed something more fundamental!). - since we're later summing, in
divide,Solved0/1 presumably signifies failure/success? - in
conquer, what does it mean to add,aandb, ofPending? What do those 2 values (asCents) signify, and what would their sum mean in this context? - in
conquer, I would have expected we just need to sum theSolveds, and the author touches on this, but it's not clear, yet, how thePendingcase is contributing (eg fixingconquer (Pending a b) = 11does have an adverse impact on functionality, and it is probably a clue thatwaysToMakeChangereturns11, or whatever constant that case is fixed to). - in
conquer,aandbareCents, whereas individethey'reChangePuzzleArgs(aka([Cent], Cent)) - where does that transformation occur?
Note: being new to SO, I was not able to comment below the original answer, which may have been more appropriate, but I hope this is also useful as is.