patriques
patriques

Reputation: 5201

How to do multiple actions in Haskell

Im trying to write a simple function to learn the IO monad in Haskell. The function is supposed to take the sum of some given integers from the console but when the function has run for example 4 times it says "1*** Exception: Char.digitToInt: not a digit '\n'"

import Data.Char

readInts :: IO ()
readInts = do
    putStrLn "Choose nr of ints to sum."
    c <- getChar
    let i = digitToInt c
    -- code.
    let sum = getInts i
    let str = "\nSum is: " ++ [intToDigit i].
    putStrLn str

getInt :: IO Int 
getInt = do
    c <- getChar
    return (digitToInt c)

getInts :: Int -> IO Int
getInts n = if n == 0 
               then return 0
               else 
                   do i <- getInt
                      j <- getInts
                      return (i+j)  

Can somebody please explain where my recursion is going wrong?

Upvotes: 1

Views: 2331

Answers (2)

leftaroundabout
leftaroundabout

Reputation: 120711

You're simply using the wrong tools to convert between "keyboard data" and numbers. Has little to do with IO.

intTodigit, as the name says, acts on single digits / characters, not on general numbers. What you want is to read / print entire strings, which can handle multi-digit numbers. Replace getChar with getLine, digitToInt with read, and [intToDigit i] with show i. Then it should work fine.

However, it would be better to make some more simplifications.

  • getInt basically exists already, though in more general form: readLn gets a line from stdin and inteprets it as the required type.
  • getInts is implemented more complicately than necessary. Explicit recursion over a counting variable (BTW, it has to be getInts (n-1) in the recursion) is ugly; such looping is obviously so common that there exists a standard solution (you need to import Control.Monad) for it which looks alot like loops in imperative languages you might be used to:

    getIntsAndSum :: Int -> IO Int
    getIntsAndSum n = fmap sum . forM [1 .. n] $ \k -> do
                             i <- getInt
                             return i
    

    which can in fact be further simplified to

    fmap sum . forM [1 .. n] $ \_ -> getInt
    

    because do blocks a just an optional construct to help you chain actions, but when there's only one action you can simply write that on its own.

  • Ideally, you would not have to first ask for the number of numbers at all: just gather all numbers you're given, and sum them up. This works, as jamshidh said, very simply with interact.

    main = interact processInput
     where processInput allInput = show (sum allNumbers)
            where allNumbers = map read . lines allInput
    

    and that's it! Nothing else needed. In fact, this can be written yet simpler: you basically have just one simple data-flow pipeline.

    main = interact $ show . sum . map read . lines
    

Upvotes: 2

jamshidh
jamshidh

Reputation: 12070

getChar gets every char, including any "\n" that you type in (which you need to submit the answer). Try filtering these out.

A better way to solve this could be to use "interact" to get the user input, and break apart the data using lines. That way the user could input multi digit numbers, and the "\n"'s would be removed.

The following sums numbers entered at the command line (without any prompting, end inputting by typing ^d).

main = interact (show . sum . fmap read . lines)

Upvotes: 2

Related Questions