I'm reading through Write Yourself a Scheme after finishing going through Learn You a Haskell. I attempted one of the early exercises: writing a program to get an operator and two numbers and do a computation. It works fine.
Things I would like to know:
How should I structure a program, in terms of building larger functions out of smaller functions? Are there redundancies in my code?
What's the most effective way to use the Maybe type to indicate failure when main is of type IO ()
? Is my checkSuccess
an appropriate way to do this?
module Main where
import System.Environment
-- parses the first arithmetic operator in a string
parseOperator :: String -> Maybe Char
parseOperator [] = Nothing
parseOperator (x:xs)
| x == '*' = Just '*'
| x == '/' = Just '/'
| x == '+' = Just '+'
| x == '-' = Just '-'
| otherwise = parseOperator xs
parseNum :: String -> Maybe Double
parseNum x =
let parsed = reads x :: [(Double,String)]
in case parsed of
[(a,"")] -> Just a
[(_,_)] -> Nothing
[] -> Nothing
compute :: Maybe Char -> Maybe Double -> Maybe Double -> Maybe Double
compute Nothing _ _ = Nothing
compute _ Nothing _ = Nothing
compute _ _ Nothing = Nothing
compute (Just c) (Just x) (Just y)
| c == '*' = Just $ x * y
| c == '/' = Just $ x / y
| c == '+' = Just $ x + y
| c == '-' = Just $ x - y
checkSuccess :: Maybe Double -> IO ()
checkSuccess Nothing = putStrLn "Failed. Check correctness of inputs"
checkSuccess (Just r) = putStrLn $ "Result: " ++ (show r)
runSequence :: String -> String -> String -> IO ()
runSequence os xs ys =
checkSuccess $ compute (parseOperator os) (parseNum xs) (parseNum ys)
main = do
putStrLn "Enter operator: * / + -"
operator <- getLine
putStrLn "Enter first number"
first <- getLine
putStrLn "Enter second number"
second <- getLine
runSequence operator first second
2 Answers 2
I'd further suggest to split compute
into two parts:
Getting the right operation from a given character:
op :: Char -> Maybe (Double -> Double -> Double) op '*' = Just (*) op '/' = Just (/) op '+' = Just (+) op '-' = Just (-) op _ = Nothing
This accepts a character, and either produces a function corresponding to the character, or
Nothing
.Applying the operation on values, taking into account all possible
Maybe
s:compute' :: Maybe Char -> Maybe Double -> Maybe Double -> Maybe Double compute' c x y = (c >>= op) <*> x <*> y
Here
c >>= op
results intoMaybe (Double -> Double -> Double)
, where the result isNothing
if the character isNothing
or if it doesn't match any supported operation. And then we use twice<*>
fromControl.Applicative
, which, specialized forMaybe
, appliesMaybe (a -> b)
toMaybe a
yeldingMaybe b
.
I didn't try it out, but you can write something like
parseOperator (x:xs)
| x `elem` "*/+-" = Just x
| otherwise = parseOperator xs
And you could write
compute :: Maybe Char -> Maybe Double -> Maybe Double -> Maybe Double
compute (Just '*') (Just x) (Just y) = Just $ x * y
compute (Just '/') (Just x) (Just y) = Just $ x / y
compute (Just '+') (Just x) (Just y) = Just $ x + y
compute (Just '-') (Just x) (Just y) = Just $ x - y
compute _ _ _ = Nothing
Explore related questions
See similar questions with these tags.