4
\$\begingroup\$

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
200_success
145k22 gold badges190 silver badges478 bronze badges
asked Aug 28, 2014 at 13:01
\$\endgroup\$

2 Answers 2

3
\$\begingroup\$

I'd further suggest to split compute into two parts:

  1. 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.

  2. Applying the operation on values, taking into account all possible Maybes:

    compute' :: Maybe Char -> Maybe Double -> Maybe Double -> Maybe Double
    compute' c x y = (c >>= op) <*> x <*> y
    

    Here c >>= op results into Maybe (Double -> Double -> Double), where the result is Nothing if the character is Nothing or if it doesn't match any supported operation. And then we use twice <*> from Control.Applicative, which, specialized for Maybe, applies Maybe (a -> b) to Maybe a yelding Maybe b.

answered Aug 28, 2014 at 18:21
\$\endgroup\$
2
\$\begingroup\$

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
answered Aug 28, 2014 at 15:26
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.