2

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 ?

0

3 Answers 3

7

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 
Sign up to request clarification or add additional context in comments.

Comments

4
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).

1 Comment

Actually, list by itself is already diverging since ordList is strict on the spine.
3

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.