Aelin
Aelin

Reputation: 301

Haskell read n numbers from user and return their sum

I'm trying to write a function isums that reads n numbers from user and returns their sum. Also, after each number, the sum up to that number is printed. I have this so far:

isums :: Int -> IO Int
isums n = do
    num <- readLn
    putStrLn (show (num + sum))
    sum <- isums (n - 1)
    return (num + sum)

Also I'm not using IORef.

Upvotes: 3

Views: 1422

Answers (3)

Artem Pelenitsyn
Artem Pelenitsyn

Reputation: 2578

Here is one more solution: it builds on the last version of the @Willem's one, but instead of using a list of ()'s (which is a bit underwhelming) as a fuel for the loop (foldM) it applies a list of actions for reading the values.

import Control.Monad

isums n = foldM go 0 (replicate n readLn) 
  where
    go s a = do 
      x <- a
      let s' = s + x 
      print s'
      return s'

Here replicate n readLn creates a list of actions, each of which reads an integer. These actions are not evaluated till the go is called during the looping by means of foldM. The fact that we can create such a list without performing actual reading stems from the Haskell's laziness.

Upvotes: 2

hnefatl
hnefatl

Reputation: 6037

This would probably be easiest to express using a helper function, as the extra requirement of printing the partial sums after each input adds a bit of extra clutter:

isums :: Int -> IO Int
isums n = helper n 0
  where
    helper 0 acc = return acc
    helper m acc = do
        x <- readLn
        let x' = acc + x
        print x'
        helper (m - 1) x'

What you're doing is kinda like a fold (look at foldM), only instead of traversing a list, you're getting the values "to be folded" from IO. If we had a function:

accM :: Monad m => (a -> m a) -> m a -> Int -> m a
accM f acc 0 = acc
accM f acc n = accM f (acc >>= f) (n - 1)

Then we could write this as:

isums :: Int -> IO Int
isums n = accM helper (return 0) n
  where
    helper acc = do
        x <- readLn
        let x' = acc + x
        print x'
        return x'

Which is a bit nicer (and more reusable) as it lets us separate the general behaviour (accM) from the specific behaviour (helper).

Upvotes: 3

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 476534

You can use replicateM :: Applicative m => Int -> m a -> m [a] for this:

import Control.Monad(replicateM)

isums :: (Read n, Num n) => Int -> IO n
isums n = do
    numbers <- replicateM n readLn
    return (sum numbers)

So here we repeat readLn the given number of times, and then we return the sum of the numbers list.

An equivalent can be achieved with an fmap:

import Control.Monad(replicateM)

isums :: (Read n, Num n) => Int -> IO n
isums n = fmap sum (replicateM n readLn)

Or even pointfree (and pointless):

import Control.Monad(replicateM)

isums :: (Read n, Num n) => Int -> IO n
isums = fmap sum . flip replicateM readLn

We can also produce a list of partial sums with scanl:

import Control.Monad(replicateM)

isums :: (Read n, Num n) => Int -> IO [n]
isums = fmap (scanl (+) 0) . flip replicateM readLn

and then later process the list, or in case we need to print these, and return the last one, we can perform a mapM on that list, like:

import Control.Monad(replicateM)

isums :: (Read n, Num n) => Int -> IO ()
isums n = fmap (scanl (+) 0) (replicateM n readLn) >>= mapM_ print

Or in case we need to print the partial sums:

isums :: (Read n, Num n, Show n) => Int -> IO n
isums n = foldM f 0 (replicate n ())
    where f a _ = readLn >>= \b -> let c = a + b in print c >> return c

Upvotes: 2

Related Questions