I've created an RPN evaluator in Haskell, as an exercise because I'm new to this language.
It runs well:
$ ./rpn
2 3 4 5 + - +
-4
And the source code:
{-# LANGUAGE BangPatterns #-}
import Data.String
import System.IO
data Token = TNum Int | TOp Operator
data Operator = Add | Sub | Mul | Div
main :: IO ()
main = do
line <- getLine
let tokens = tokenise line
(numc, opc) = countTok tokens
!junk =
if numc == opc + 1
then ()
else error "Not a correct expression."
print $ eval [] tokens
tokenise :: String -> [Token]
tokenise = map str2tok . words
eval :: [Int] -> [Token] -> Int
eval (s:_) [] = s
eval stack (TNum t:ts) = eval (t : stack) ts
eval (x:y:stacknoxy) (TOp t:ts) = eval (applyOp t y x : stacknoxy) ts
str2tok :: String -> Token
str2tok tkn@(c:_)
| c `elem` ['0'..'9'] = TNum (read tkn :: Int)
| otherwise = TOp $ case tkn of
"+" -> Add
"-" -> Sub
"*" -> Mul
"/" -> Div
_ -> error $ "No such operator " ++ tkn
applyOp :: Operator -> Int -> Int -> Int
applyOp Add a b = a + b
applyOp Sub a b = a - b
applyOp Mul a b = a * b
applyOp Div a b = a `div` b
countTok :: [Token] -> (Int, Int)
countTok [] = (0, 0)
countTok (t:ts) =
let (x, y) = case t of
TNum _ -> (1, 0)
_ -> (0, 1)
in (x, y) `addPair` countTok ts
addPair :: (Num a, Num b) => (a, b) -> (a, b) -> (a, b)
addPair (x, y) (z, w) = (x + z, y + w)
How can this code be improved? I hope my implementation is elegant, and if it isn't - what are the ways to clean it up? In particular, I really don't like the error
s because they're just ugly:
./rpn
5 6 + +
rpn: Not a correct expression.
CallStack (from HasCallStack):
error, called at rpn.hs:16:22 in main:Main
I know they can be replaced with fail
, which has a much nicer output, but from what I've read it can only be done inside a function that returns IO ()
.