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.0
primitiveSolver "2 3 4 + *"
→ 14.0
primitiveSolver "5 !"
→ 120.0
primitiveSolver "2 3 **"
→ 8.0
primitiveSolver "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.