Below we have some mathematical binary arguments that you may recognize from homework 2. > data Binop = > Plus -- + :: Int -> Int -> Int > | Minus -- - :: Int -> Int -> Int > | Times -- * :: Int -> Int -> Int > | Divide -- / :: Int -> Int -> Int > deriving (Show) applyOp performs these operations, but unlike in the homework, you now must consider errors (represented by 'Nothing'). > applyOp :: Binop -> Maybe Int -> Maybe Int -> Maybe Int Plus is done for you. Notice how code must check for 'Nothing' for each operand. > applyOp Plus mi mj = > case mi of > Nothing -> Nothing > Just i -> > case mj of > Nothing -> Nothing > Just j -> Just $ i + j Minus is also done for you. This case **does** use monads, but without the do syntax. > applyOp Minus mi mj = > mi >>= (\i -> mj >>= (\j -> Just $ i - j)) Implement Times and Divide. Try the Times case without monads (as we did with the Plus case). > applyOp Times mi mj = > case mi of > Nothing -> Nothing > Just i -> > case mj of > Nothing -> Nothing > Just j -> Just $ i * j For the Divide case, use bind (>>=) as we did for Minus. On an attempt to divide by 0, return Nothing as the answer. > applyOp Divide mi mj = > mi >>= (\i -> mj >>= (\j -> if j == 0 then Nothing else Just $ i `div` j)) The following test cases will help you verify your changes. > testapp1 = applyOp Minus (applyOp Times (Just 3) (Just 4)) $ applyOp Divide (Just 8) (Just 2) > testapp2 = applyOp Minus (applyOp Times (Just 3) (Just 4)) $ applyOp Divide (Just 8) (applyOp Plus (Just 3) (Just (-3))) Now implement applyOp', which implements all methods using the do syntax. The Plus case is done for you once again. Be sure to check for zero with Divide. > applyOp' :: Binop -> Maybe Int -> Maybe Int -> Maybe Int > applyOp' Plus mi mj = do > i <- mi > j <- mj > return $ i + j > applyOp' Minus mi mj = do > i <- mi > j <- mj > return $ i - j > applyOp' Times mi mj = do > i <- mi > j <- mj > return $ i * j > applyOp' Divide mi mj = do > i <- mi > j <- mj > if j == 0 > then Nothing > else return $ i `div` j More test cases: > testapp1' = applyOp' Minus (applyOp' Times (Just 3) (Just 4)) $ applyOp' Divide (Just 8) (Just 2) > testapp2' = applyOp' Minus (applyOp' Times (Just 3) (Just 4)) $ applyOp' Divide (Just 8) (applyOp' Plus (Just 3) (Just (-3))) Finally, note the following function for incrementing and decrementing ints. > mincr :: Int -> Maybe Int > mincr i = Just $ i + 1 > mdecr :: Int -> Maybe Int > mdecr i = Just $ i - 1 Experiment with these functions and the >>= syntax. Here is one example: > testIncDec = Just 7 >>= mincr >>= mincr >>= mincr >>= mdecr > testIncDec' = do > i <- Just 7 > i <- mincr i > i <- mincr i > i <- mincr i > i <- mdecr i > return i Does bind seem more natural in this case than using do? Why or why not? The bind behaves sort of like an upgraded pipe `|` from shell, where the output of one function is piped into the next function, but with the added benefit of error handling The do syntax is a bit more explicit in this case, as it requires unwrapping the value, performing the operation, and then wrapping the value back up > main :: IO () > main = do > print testapp1 > print testapp2 > print testapp1' > print testapp2' > print testIncDec > print testIncDec'