In this project, we’ll build a small expression evaluator using Reverse Polish Notation (RPN) —
also called postfix notation. The idea is simple: instead of writing
3 + 4, you write 3 4 +. This lets you evaluate expressions without worrying about
parentheses or operator precedence.
The project is a fun and compact way to learn about:
foldl
We want a function that can take a string like "3 4 +" and return 7.0.
Or more complex ones like "2 3 4 + *" → 14.0.
We’ll also add support for basic arithmetic, powers, exponentials, logs, and even a factorial!
primitiveSolver :: String -> Double
primitiveSolver expr = head . foldl funcNPI [] . words $ expr
where funcNPI (x:ys) "!" = (factorial2 x):ys
funcNPI (x:ys) "exp" = (exp x):ys
funcNPI (x:ys) "log" = (log x):ys
funcNPI (x:y:ys) "**" = (x ** y):ys
funcNPI (x:y:ys) "*" = (x * y):ys
funcNPI (x:y:ys) "/" = (x / y):ys
funcNPI (x:y:ys) "+" = (x + y):ys
funcNPI (x:y:ys) "-" = (x - y):ys
funcNPI xs numberString = read numberString : xs
factorial2 :: (Floating a, Eq a) => a -> a
factorial2 0 = 1
factorial2 n = (*) n . factorial2 $ n - 1
foldl and the stackThe key idea is that RPN can be evaluated using a stack. You scan the expression token by token:
The expression is split into tokens using words, and foldl traverses them left to right,
maintaining the stack as its accumulator.
At the end, head retrieves the top of the final stack — the result.
funcNPI functionThis helper function defines what to do for each possible token:
(x:ys) "!" → apply factorial to the top of the stack(x:ys) "exp" → apply exponential(x:ys) "log" → apply natural logarithm(x:y:ys) "**" → exponentiation*, /, +, -) pop two elementsread and push onto the stack
The function relies heavily on pattern matching to destructure the stack
(x:y:ys means at least two elements available).
factorial2 helperThis is a recursive factorial function defined for floating types:
factorial2 0 = 1
factorial2 n = (*) n . factorial2 $ n - 1
It’s written in a point-free style using function composition
((*) n . factorial2 $ n - 1) instead of the more traditional
n * factorial2 (n - 1) — a good reminder that Haskell lets you write recursion in many elegant ways.
primitiveSolver "3 4 +" → 7.0primitiveSolver "2 3 4 + *" → 14.0primitiveSolver "5 !" → 120.0primitiveSolver "2 3 **" → 8.0primitiveSolver "1 exp" → 2.718281828...This “primitive solver” might look small, but it’s a perfect example of Haskell’s power: with just a few lines, you can build a stack-based interpreter capable of evaluating complex expressions.
And because it’s written in pure functional style, it reads more like a description of how to evaluate an expression than a step-by-step algorithm. That’s the beauty of Haskell.