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