Home > Mobile >  Validate a list of positive integer numbers in Haskell
Validate a list of positive integer numbers in Haskell

Time:01-10

I want to write an Haskell program that read a list of integer positive numbers from stdin, if user write a different thing like a list of negative numbers, a list of characters or a thing that isn't a list, program need to advise user and read again from stdin until user write a correct list. This is what I wrote but if user type a list that contain a char, or a single single digit/char without close it in square brackets, program ends. Instead if user type a list containing negative numbers, or empty list, program works well. Thanks in advice.

main :: IO()
main = do  
  putStrLn "\nType a list of positive integers enclosed in square brackets and separated by commas:"
  list <- readIntList
  putStrLn "\nList:"
  print list

readIntList :: IO [Double]
readIntList = do
  readedList <- getLine
  let list = read readedList
  case checkList list && not (null list) of
    True -> return list
    False -> putStrLn "\nInvalid input, type again:" >> readIntList

checkList :: [Double] -> Bool
checkList = all checkNumber

checkNumber :: (Ord a, Num a) => a -> Bool
checkNumber n
  | n > 0 = True
  | otherwise = False

CodePudding user response:

First, let's take a look to your solution and what's wrong with it. Errors are marked with a comment.

main :: IO()
main = do  
  putStrLn "\nType a list of positive integers enclosed in square brackets and separated by commas:"
  list <- readIntList
  putStrLn "\nList:"
  print list

readIntList :: IO [Double]
readIntList = do
  readedList <- getLine
  -- This will fail!! at run time. read function expect wellform input, 
  -- Therefore, you can't control the errors this way
  let list = read readedList
  -- This is kind of right, but it could be done better by pattern matching
  case checkList list && not (null list) of
    True -> return list
    False -> putStrLn "\nInvalid input, type again:" >> readIntList

-- These two can be simplify into one. See below
checkList :: [Double] -> Bool
checkList = all checkNumber

checkNumber :: (Ord a, Num a) => a -> Bool
checkNumber n
  | n > 0 = True
  | otherwise = False

Now, we know what's wrong with your implementation. Let's fix it

import Text.Read

main :: IO()
main = do  
  putStrLn "\nType a list of positive integers enclosed in square brackets and separated by commas:"
  list <- readIntList
  putStrLn "\nList:"
  print list

readIntList :: IO [Double]
readIntList = do
  readedList <- getLine
  -- use readMaybe from Text.Read module. It returns Nothing if the input is badform
  -- returns, Just xs otherwise
  case readMaybe readedList of
    Nothing -> do
      -- This is the case when input isn't a list of numbers
      putStrLn "Invalid Input type. Try again:\n"
      readIntList
    Just [] -> do
      -- This is the case of an empty list input.
      putStrLn "Input must be not empty. Try again:\n"
      readIntList
    Just xs ->
      -- This is the case input is a nonempty list of numbers, but we don't know if positive
      --  hence we must check all positive numbers.
      if checkList xs
        then pure xs
        else do 
          putStrLn "all input numbers must be positive. Try again:\n" 
          readIntList

-- Checking all positives numbers is as simple as this one.
checkList :: [Double] -> Bool
checkList = all (>0)

For the sake of completeness, the last pattern match can be simplify using guards, but it isn't that known syntax

  case readMaybe readedList of
    --  ... other patterns as before 
    Just xs 
      | checkList xs -> pure xs
      | otherwise ->  do 
          putStrLn "all input numbers must be positive. Try again:\n" 
          readIntList
  • Related