The correct approach for these difficult math problems is a DSL. So I'll model this in terms of a simple language
data DSL b a = Var x (b -> a)
| Mult DSL DSL (b -> a)
| Plus DSL DSL (b -> a)
| Const Integer (b -> a)
To write our DSL nicely, it's helpful to view it as a free monad generated by the algebraic functor
F X = X + F (DSL b (F X)) -- Informally define + to be the disjoint sum of two sets
We could write this in Haskell as
Free b a = Pure a
| Free (DSL b (Free b a))
I will leave it to the reader to derive the trivial implementation of
join :: Free b (Free b a) -> Free b a
return :: a -> Free b a
liftF :: DSL b a -> Free b a
Now we can descibe an operation to model a factorial in this DSL
factorial :: Integer -> Free Integer Integer
factorial 0 = liftF $ Const 1 id
factorial n = do
fact' <- factorial (n - 1)
liftF $ Mult fact' n id
Now that we've modeled this, we just need to provide an actual interpretation function for our free monad.
denote :: Free Integer Integer -> Integer
denote (Pure a) = a
denote (Free (Const 0 rest)) = denote $ rest 0
...
And I'll leave the rest of the denotation to the reader.
To improve readability, it's sometimes helpful to present a concrete AST of the form
data AST = ConstE Integer
| PlusE AST AST
| MultE AST AST
and then right a trivial reflection
reify :: Free b Integer -> AST
and then it's straightforward to recursively evaluate the AST.