laker001
laker001

Reputation: 417

Reading numbers from input Haskell

I want to have a function that reads arbitrary int's until the number '0' is inserted, and then presents the numbers inserted in an ordered list.

For that i wrote this function:

import Data.List

readIntegers :: IO()
readIntegers = do
    putStrLn "insert a number: "
    num<-getLine
    let list = ordList ((read num :: Int):list)
    if (read num == 0)  
    then print list 
    else readIntegers
  where ordList ::[Int]->[Int]
        ordList [] = []
        ordList xs = sort xs

This compiles just fine, but when i insert the number '0', it gives me this error:

*** Exception: <<loop>>

What am i doing wrong ?

Upvotes: 2

Views: 3124

Answers (3)

ErikR
ErikR

Reputation: 52029

For a more sophisticated solution, note that this is an unfold and you can use unfoldM from Control.Monad.Loops to implement it:

import Control.Monad.Loops (unfoldM)

readInts :: IO [Int]
readInts = unfoldM $ fmap (check . read) getLine
  where check x = if x == 0 then Nothing else Just x

This has the nice property that it returns the list in the order in which it was read.

Upvotes: 3

shree.pat18
shree.pat18

Reputation: 21757

As @phg points out, you are essentially constructing an infinite list, and actually evaluating it causes the loop error. A simple implementation to resolve this issue is to define a helper function which takes an additional parameter - a list to store all the inputs read in from the screen, like so:

readInteger :: IO ()
readInteger = readInteger' []
    where
        readInteger' x = do
        putStrLn "insert a number: "
        num<-getLine        
        if ((read num :: Int) == 0)  
        then print $ ordList x 
        else readInteger' $ (read num :: Int):x
        where ordList ::[Int]->[Int]
              ordList [] = []
              ordList xs = sort xs

Please note that the above is essentially just an implementation of @phg's answer, but with some changes to your original logic. Firstly, since 0 is a sentinel value, we shouldn't be appending that to our list. Second, we do not need to sort the list every single time we are adding a value to it. Sorting once at the time of printing/passing to another function is sufficient.

Demo

If you want to read an unspecified number of integers without prompting for user input and cut it off the moment you encounter 0, you would probably do well to use getContents, which will read everything from the standard input as a single string, lazily.

Then, it is a simple matter of parsing it to a list of numbers and doing what you want with it, like so:

readIntegers :: ()
readIntegers = do
   a <- getContents
   let b = ordList $ takeWhile (/= 0) $ map (\x -> read x :: Int) $ words a          
   mapM (putStrLn . show) b
 where ordList ::[Int]->[Int]
       ordList [] = []
       ordList xs = sort xs 

Upvotes: 7

phipsgabler
phipsgabler

Reputation: 20950

let list = ordList ((read num :: Int):list)

This is basically a recursive definition of a list of the form [x, x, ...] (like if you wrote an equation saying x = 1 + x). That is perfectly fine by itself, since Haskell is lazy; however, if you try to print list (aka "solve the equation"), it will fail, since it will try to print infinitely many numbers.

You probably have a misconception about the workings of the (:) operator. Haskell functions will never perform an assignment operation and concatenate num onto list by changing it, like in imperative languages. There are only pure functions.

If you want to accumulate all numbers, you should try to come up with a recursive definition of readIntegers, keeping its state (the list) in an additional parameter (there are also more sophisticated ways, hiding the state passing, but more complicated to use for a beginner).

Upvotes: 4

Related Questions