6

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?

leppie
  • 115,091
  • 17
  • 196
  • 297
ozhank
  • 69
  • 4
  • I think it's fine - you could shorten some parts (for example *merge* `read` and `getLine` - using [`<$>`](http://haddocks.fpcomplete.com/fp/7.4.2/20130829-168/base/Data-Functor.html#v:-60--36--62-) and `div`/ `mod` using [`divMod`](http://haddocks.fpcomplete.com/fp/7.4.2/20130829-168/base/Prelude.html#v:divMod) - but I think it's fine for such a simple program – Random Dev Sep 05 '14 at 05:17
  • Style is mostly a preference, but I would do the following: `input <- putStrLn "prompt" >> getLine`; sequential let statements don't need a let on each line (ie replace all the lets after the first with 3 spaces), instead of having `putStrLn` in sequence, I would write `putStrLn $ "line 1" ++ "line 2" ++ "line 3"` (each string can be on its own line - as long as following lines are indented, haskell knows its part of the same statement). – user2407038 Sep 05 '14 at 05:27
  • 1
    @user2407038 your `putStrLn` will miss some newlines ;) – Random Dev Sep 05 '14 at 05:32
  • 2
    Since you are not validating the input (to see if it really can be parsed as a String) anyway, you might as well use readLn directly instead of getLine. – Sarah Sep 05 '14 at 05:33
  • 5
    You might get a more thorough response on http://codereview.stackexchange.com/ which is a site specifically for code reviews. – Tikhon Jelvis Sep 05 '14 at 05:50

1 Answers1

8

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

Community
  • 1
  • 1
AndrewC
  • 32,300
  • 7
  • 79
  • 115