user289661
user289661

Reputation: 179

Haskell - loop returning user input integer

I want to write a function which, when called, will relentlessly ask for user input until the input can be read as an integer, (at which point the integer is returned to a possible do block where the function was called in the first place)

My code here:

lp_reqInt = 
  do
    input1 <- getLine
    if ((readMaybe input1 :: Maybe Int) == Nothing)
      then do 
             putStrLn "(integer input required, please try again)"
             lp_reqInt 
      else let output = fromMaybe (-666) (readMaybe input1 :: Maybe Int)
    return output

trying to compile this gives the suspiciously simple error of parse error (possibly incorrect indentation or mismatched brackets) for the last line. (No indent characters were used throughout the whole file)

How should I change my code to have the intended behaviour? Is that even possible?

Upvotes: 0

Views: 641

Answers (3)

Redu
Redu

Reputation: 26161

Previous answers are great but i just would like to extend this topic with another reasonable approach for those who end up here searching not exactly what the OP is asking for but something relevant.

Since the topic mentions User Input (IO) and Integer (Maybe Int) we end up with a type like IO (Maybe Int). Such types are best expressed under the Monad Transformers, namely MaybeT IO Int and they act nicely as Alternative class members as well.

Haskell has fantastic solutions for these cases such that we may approach the same problem like;

import Control.Monad (msum)
import Control.Monad.Trans.Maybe
import Control.Monad.Trans (lift)
import Text.Read (readMaybe)

lp_reqInt :: MaybeT IO Int
lp_reqInt = msum . repeat $ (lift . putStrLn) "Enter an integer.." >>
                            (MaybeT $ readMaybe <$> getLine)

It's relentless :)

λ> runMaybeT lp_reqInt
Enter an integer..
boru
Enter an integer..
not an integer
Enter an integer..
42
Just 42

Upvotes: 0

Daniel Wagner
Daniel Wagner

Reputation: 152682

The other answer discusses what was wrong, and the minimal fix. In addition to the minimal thing that will get you moving on with your code, I thought it might also be interesting to show the idiomatic fix, namely, to use pattern matching instead of if. So:

lp_reqInt :: IO Int
lp_reqInt = do
  input1 <- getLine
  case readMaybe input1 of
    Nothing -> do
      putStrLn "(integer input required, please try again)"
      lp_reqInt
    Just n -> return n

This doesn't require the use of the weird fall-back -666 in fromMaybe, which is nice. Using pattern matching instead of (==) also has a more subtle advantage: it doesn't require the underlying type to have an Eq instance. For Int there is one, so there's no advantage in this code, but in other situations it can matter more. I've also lifted the type signature to the top-level; see here for further discussion of this idiom.

Upvotes: 4

AJF
AJF

Reputation: 11913

You seem to be slightly misunderstanding how do-notation works.

I'll give you a 'correct' version and we can work off that:

lp_reqInt = do
    input1 <- getLine
    let maybeInput = readMaybe input1 :: Maybe Int
    if maybeInput == Nothing
      then do putStrLn "(integer input required, please try again)"
              lp_reqInt 
      else return $ (\(Just x) -> x) maybeInput

Note the let-statement at the top there. I can do a let-statement rather than a let-in-statement here, because it is in the top level of a do-block. When you wrote let output = fromMaybe (...), that was not in the top level of a do-block, that was in the second part of an if-statement, hence it will not work.

You were getting a parse error for this very reason: GHC expected an accompanying in!

Upvotes: 1

Related Questions