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.