Reputation: 5201
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
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
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