In the following program, the function
evalrpn takes a list of tokens and returns, through
show, the string representation of the number that results from evaluatinng the respective RPN expression, or the string
"error". Reading the input, doing initial processing on it, and printing the outcome of
evalrpn is the responsibility of
getContents, the entire input stream is obtained as a single string. However, reading is actually lazy, progressing only as far as there is demand for it, which in particular makes it possible to be interlaced with printing the values of the RPN expressions. The result of
getContents is passed to a composition of several transformations (dot (
.) is the composition operator).
lines splits the input into lines, thus producing a list of strings.
map words then breaks each line down to a list of individual tokens. Further on, any possible empty lists of tokens (i.e., all empty lines) are
filter-ed out. Finally, each remaining line, as a list of tokens, is
reverse-d, passed to
evalrpn, and the result of the latter is printed. The composition of these three actions that constitute the processing of a single line is applied through
mapM_, which is the monadic (in the Haskell sense of the word monad) variant of
map. This, together with the lazyness of the language and the use of the (also monadic) operator
>>= to communicate the result of
getContents to the composition of functions that processes it, ensures that the input and output are properly sequenced as required by the interactive nature of the calculator. Explicitly setting a line-level buffering for the standard input and output channels, so as to keep/flush the buffer of each at the right times is still needed, though, hence done before any reading takes place.
evalrpn uses a locally defined function,
eval, to do the real work of evaluating an expression.
eval is recursive on its argument – the reversed list of RPN expression tokens. It checks whether a token
tk is a number. If
tk is not a number, but appears to be an operator, and if the two recursive calls of
eval on the rest of the token list are successful in producing arguments
x for that operator,
tk is mapped (by means of the inner
case expression) to the corresponding function and the latter is applied to
Both the parsing of a token as a number and the extraction of the correspondng numeric value are done entirely within the program – the part after the
let, resulting in computing the value
maybenum. This is not because Haskell does not have its own library functions for parsing numeric values, but because these functions – and the language itself – accept no leading or trailing dots in floating-point numeric denotations. Neither do they accept numbers with a + arithmetic sign.
After doing all the necessary checks on
tk in order to ensure that it represents a number according to our specification, the respective numeric value gets stored in
maybenum as a value of type
Maybe, such as e.g.
Just 217 or
Just 23.8. If
tk turns to be not a number,
maybenum holds the value
Nothing (also of type
Maybe). The remainder of the program ‘knows’ if
tk was a number by distinguishing whether
maybenum was constructed by
Just or not.
Notably, it is precisely because of the explicit parsing of numbers that the program has to import library functions from the modules
In order to keep track of the consumption of the token list across its recursive self-invocations,
eval returns a pair where the second item is the unused part of its argument – a list of tokens. The first item in the pair is the computed number. An RPN expression is successfully evaluated if the top-level call of
eval returns an empty list
z. In case of syntax error in the expression, including
eval calling itself with an empty list,
eval returns (an insignificant
0 and) a list with a single empty string in it. That way, an error is propagated upwards to the top-level call of
eval and eventually leads to returning
import System.IO; import Data.List; import Data.Char; import Data.Maybe main = do hSetBuffering stdin LineBuffering hSetBuffering stdout LineBuffering getContents >>= mapM_ (putStrLn . evalrpn . reverse) . filter (/=) . map words . lines evalrpn tks = if null z then show r else "error" where (r,z) = eval tks eval  = (0,[""]) eval (tk:tks) = let h:t = tk tk1 = if elem h "+-" then t else tk tk2 = delete '.' tk1 sgn = if h=='-' then -1 else 1 dp = fromMaybe 0 (findIndex (=='.') (reverse tk1)) cnum v d = 10*v + fromIntegral (digitToInt d) maybenum = if tk2== || not (all isDigit tk2) then Nothing else Just (sgn * foldl cnum 0 tk2 / 10^dp) in case maybenum of Just r -> (r,tks) _ -> if t== && elem h "+-*/" && z2/=[""] then ((case tk of "+" -> (+); "-" -> (-) "*" -> (*); "/" -> (/)) x y, z2) else (0,[""]) where (y,z1) = eval tks (x,z2) = eval z1