I implemented the following (what I believe to be) safe Reverse Polish Calculator.
Now I'm using foldM
to take advantage of the power of Monads. Previously, I was not.
Can you please critique my code?
import Data.Maybe
import Text.Regex
import Control.Monad
import Network.CGI.Protocol
data Operator = Add | Subtract | Multiply
-- safe Reverse Polish Notation calculator
rpn :: String -> Maybe Double
rpn xs = maybeStringToMaybeDouble result :: Maybe Double
where result = safeHead $ foldM foldingOp [] $ words xs
--foldM :: Monad m => (a -> b -> m a) -> a -> [b] -> m a
maybeStringToMaybeDouble :: Maybe String -> Maybe Double
maybeStringToMaybeDouble x = do
y <- x
maybeRead y
-- extract maybeHead of Maybe [a]
safeHead :: Maybe [a] -> Maybe a
safeHead m = do
x <- m -- x has type [a]
listToMaybe x
foldingOp :: [String] -> String -> Maybe [String]
foldingOp acc s
| isNumber s = Just (s : acc)
| isPlusRegex s = performOp Add acc
| isMinusRegex s = performOp Subtract acc
| isMultiplyRegex s = performOp Multiply acc
| otherwise = Nothing
performOp :: Operator -> [String] -> Maybe [String]
performOp o (x:y:zs) = Just $ (doOp o x y) : zs
performOp _ _ = Nothing
doOp :: Operator -> String -> String -> String
doOp Add a b = show $ (read b :: Double) + (read a :: Double)
doOp Subtract a b = show $ (read b :: Double) - (read a :: Double)
doOp Multiply a b = show $ (read b :: Double) * (read a :: Double)
isNumber :: String -> Bool
isNumber = matches' mkNumberRegex
mkNumberRegex :: Regex
mkNumberRegex = mkRegex "^[0-9]+[.]?[0-9]*$"
isPlusRegex :: String -> Bool
isPlusRegex = matches' mkPlusRegex
isMinusRegex :: String -> Bool
isMinusRegex = matches' mkMinusRegex
isMultiplyRegex :: String -> Bool
isMultiplyRegex = matches' mkMultiplyRegex
mkPlusRegex :: Regex
mkPlusRegex = mkRegex "^[+]$"
mkMinusRegex :: Regex
mkMinusRegex = mkRegex "^[-]$"
mkMultiplyRegex :: Regex
mkMultiplyRegex = mkRegex "^[*]$"
matches' :: Regex -> String -> Bool
matches' r x
| matchRegex r x == Nothing = False
| otherwise = True