I've been learning Haskell recently and I decided I needed to work on a (somewhat) realistic problem.
What I'm most interested in getting feedback on is how well I've used the tools in Haskell to accomplish the goal of converting Roman numerals to Arabic values. I suspect that my approach is more verbose than it needs to be, so thoughts on how I could remove unneeded code would be quite welcome.
Lastly, thoughts on how to address Haskell bad practices or similar errors would be appreciated.
-- file: roman.hs import Data.Char -- Application startup. main :: IO () main = do putStrLn "Enter a Roman numeral. The numerical value will be returned." putStrLn "For example MMI -> 2001." putStrLn "Enter 'finis' to exit." process putStrLn "vale! :-)" -- Main loop. process :: IO () process = do putStrLn "Numeral: " line <- getLine if line == "finis" then return () else do let nums = readNumerals(line) in if errorNum `elem` nums then putStrLn (line ++ "is not a valid Roman numeral.") else putStrLn (" The Roman numeral " ++ line ++ " is equal to " ++ show(getValue(nums))) process data Numeral = Numeral { numeralValue :: Int, numeralSymbol :: Char, subRule :: [Char]} deriving (Show, Eq) -- This is the Numeral error type. used to check if the input was valid. errorNum = Numeral 0 'E' [] -- Turn a string into a list of numerals. readNumerals :: String -> [Numeral] readNumerals (x:xs) = readNumeral (toUpper(x)) : [] ++ readNumerals xs -- First cons the Char to an empty list, then recur. readNumerals "" = [] -- Parse each char into the corresponding numeral. readNumeral :: Char -> Numeral readNumeral 'I' = Numeral 1 'I' ['V', 'X'] readNumeral 'V' = Numeral 5 'V' [] readNumeral 'X' = Numeral 10 'X' ['L', 'C'] readNumeral 'L' = Numeral 50 'L' [] readNumeral 'C' = Numeral 100 'C' ['D', 'M'] readNumeral 'D' = Numeral 500 'D' [] readNumeral 'M' = Numeral 1000 'M' [] readNumeral _ = errorNum -- Turn a list of Roman numerals into their corresponding Arabic number. getValue :: [Numeral] -> Int getValue [] = 0 getValue (x:xs) = if subRuler (subRule x) xs then negate (numeralValue x) + getValue xs else numeralValue x + getValue xs -- if x has a non-empty subRule and the next x is in the list, then flip the sign for x -- Take the subRule from x and xs, if x of xs is in subRule, return true, otherwise false. -- What this really means is see if the next element in the list of Numerals is one of the elements in subRule. subRuler :: [Char] -> [Numeral] -> Bool subRuler [] _ = False subRuler _ [] = False subRuler a b = numeralSymbol (head b) `elem` a {- Subtractive Rules, from Wikipedia I placed before V or X indicates one less, so four is IV (one less than five) and nine is IX (one less than ten) X placed before L or C indicates ten less, so forty is XL (ten less than fifty) and ninety is XC (ten less than a hundred) C placed before D or M indicates a hundred less, so four hundred is CD (a hundred less than five hundred) and nine hundred is CM (a hundred less than a thousand) -}