First, I would convert all of the functions which might throw an error to return some kind of failable type, like Maybe Int
or Either String Token
. This includes str2tok
, tokenize
, and eval
. This would also remove the need for the countTok
function, since the program can just return an error value from eval
instead. With the help of the Monad
instances for these error types, this is a relatively simple change.
Next, I would use isDigit
from Data.Char
instead of c `elem` ['0'..'9']
because isDigit
makes less comparisons in order to determine if it's a digit.
Lastly, I would change the Operator
type to the function type Int -> Int -> Int
. This will remove the need for applyOp
and will consolidate all the places that would be necessary to change if you wanted to extend the program to accept more operators.
import Data.Char (isDigit)
data Token = TNum Int | TOp (Int -> Int -> Int)
main :: IO ()
main = do
line <- getLine
either putStrLn print $ do
tokens <- tokenize line
eval [] tokens
tokenize :: String -> Either String [Token]
tokenize = mapM str2tok . words
str2tok :: String -> Either String Token
str2tok tkn@(c:_)
| isDigit c = Right $ TNum (read tkn)
| otherwise = TOp <$> case tkn of
"+" -> Right (+)
"-" -> Right (-)
"*" -> Right (*)
"/" -> Right div
_ -> Left $ "No such operator " ++ tkn
eval :: [Int] -> [Token] -> Either String Int
eval (s:_) [] = Right s
eval stack (TNum t:ts) = eval (t : stack) ts
eval (x:y:stacknoxy) (TOp t:ts) = eval (t y x : stacknoxy) ts
eval _ _ = Left "Not a correct expression."