Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a better way of doing this in Haskell?

I have written the following to assist grand kids with their home schooling work and to keep mind working by learning how to program (I thought haskell sounded awesome).

main :: IO ()
main = do
   putStrLn "Please enter the dividend :"
   inputx  <- getLine
   putStrLn "Please enter the divisor :"
   inputy  <- getLine
   let x = (read inputx) :: Int
   let y = (read inputy) :: Int
   let z = x `div` y
   let remain = x `mod` y
   putStrLn ( "Result: " ++ show x ++ " / " ++ show y ++ " =  " ++ show z ++  " remainder " ++ show remain )

   putStrLn ( "Proof: (" ++ show y ++ " x " ++ show z ++ ") = " ++ show (y * z) ++ " + " ++ show remain ++ " = " ++ show ((y * z) + remain))
   putStrLn ( "Is this what you had? ")

Is their a neater/nicer/better/more compact way of doing this?

like image 944
ozhank Avatar asked Sep 05 '14 04:09

ozhank


1 Answers

It would benefit from a key principle: separate your pure code from your IO as much as possible. This will let your programs scale up and keep main breif. Lots of let in a big main isn't a very functional approach and tends to get much messier as your code grows.

Using a type signature and readLn which is essentially fmap read getLine helps cut down some cruft. (If you're not familiar with fmap, visit the question How do functors work in haskell?. fmap is a very flexible tool indeed.)

getInts :: IO (Int, Int)
getInts = do
    putStrLn "Please enter the dividend :" 
    x <- readLn 
    putStrLn " Please enter the divisor :"
    y <- readLn
    return (x,y)

Now the processing. If I were doing more with this kind of data, or more frequently, I'd be using a record type to store the dividend, divisor, quotient and remainder, so bear that in mind for the future, but it's an overkill here.

I'm hackishly returning a list rather than a tuple, so I can use map to show them all:

sums :: (Int, Int) -> [Int]
sums (x,y) = [x, y, q, r, y * q, y * q + r] where
            q = x `div` y
            r = x `mod` y

The final piece of the jigsaw is the output. Again I prefer to generate this outside IO and then I can just mapM_ putStrLn on it later to print each line. I'd prefer this to take the record type, but I'm tolerating a list of strings as input instead since I'm assuming I've already shown them all.

explain :: [String] -> [String]
explain [x,y,q,r,yq,yq_r] = 
  [ concat ["Result: ", x, " / ", y, " =  ", q, " remainder ", r]
  , concat ["Proof: (", y, " x ", q, ") + ", r, " = ", yq, " + ", r, " = ", yq_r]
  , "Is this what you had? "]

Now we can write main as

main = do (x,y) <- getInts
          let ns = map show ( sums (x,y) )
              es = explain ns
          mapM_ putStrLn es

or even more succinctly, by piping together the functions explain . map show . sums, and applying that to the output of getInts using fmap:

main :: IO ()
main = fmap (explain . map show . sums) getInts
       >>= mapM_ putStrLn

You might notice that I added a +r in the proof to make = always mean =, which is the correct mathematical usage, and mirror's Haskell's meaning for =.

like image 178
AndrewC Avatar answered Oct 21 '22 06:10

AndrewC