How to use readMaybe function in IO correctly

I started with programming in Haskell about 4 months ago, and now I have come to the point where I have to deal with the Haskell IO system. I already did a lot of I / O and did not encounter any problems that I could not solve on my own, but this time I searched Google for almost two hours so as not to get any information about the readMaybe function. Therefore, I have the following problem that needs to be solved, and I have already tried many different approaches to solving it, but all the time I get the same error message from my compiler:

No instance for (Read a0) arising from a use of `readMaybe' The type variable `a0' is ambiguous 

I understand what the compiler wants to tell me, but I have no idea how to solve this problem. I already tried to add a class constraint, but to no avail. So, here is my very small and simple program that simply calculates how many real numbers a user has entered. The program is designed to end when the user enters an empty string. This is just a helper function that I want to use for my project later.

 countNumbers :: IO Int countNumbers = do x <- count 0 return x where count :: Int -> IO Int count n = do line <- getLine case line of "" -> do return n _ -> case readMaybe line of Just _ -> do x <- count (n+1) return x Nothing -> do x <- count n return x 

Unfortunately, I could not find out a lot of information about the readMaybe function. The only thing I could find was in the Haskell Text.Read library:

 readMaybe :: Read a => String -> Maybe aSource Parse a string using the Read instance. Succeeds if there is exactly one valid result. 

The strangest thing for me is that I already wrote a function that uses the readMaybe function, and it worked fine ... This program just asks the user for a number and keeps asking while the user enters a valid number

 getLineInt :: IO Int getLineInt = do putStrLn "Please enter your guess" line <- getLine case readMaybe line of Just x -> do return x Nothing -> do putStrLn "Invalid number entered" x <- getLineInt return x 

As far as I can see, there are no differences between using the readMaybe function in two programs and, therefore, it works in one, but not the other :)

I would really appreciate any tips from you.

+7
haskell
source share
2 answers

This has nothing to do with IO, so you may not understand what the compiler is trying to tell you. There is a variable of type a in the readMaybe signature; a must have a Read instance, but besides it it can be anything. The compiler tells you that it has no way to determine what you want a .

In getLineInt you do not have this problem because you are returning the result of readMaybe , and the type signature says that it should be Int . In countNumbers you are not using the readMaybe result, so there is nothing that could be used to determine the correct type. You can fix this by adding an explicit type signature (I chose Int , since you are apparently counting numbers):

 _ -> case readMaybe line :: Maybe Int of 

Finally, a word about the meaning of do : it’s just syntactic sugar, you don’t have to use it all the time. Instead of do return x you can simply write return x , and instead

 x <- getLineInt return x 

you can just do

 getLineInt 

This makes reading more understandable:

 getLineInt :: IO Int getLineInt = do putStrLn "Please enter your guess" line <- getLine case readMaybe line of Just x -> return x Nothing -> putStrLn "Invalid number entered" >> getLineInt 
+13
source share

Why is this happening?

In your second function, it is clear that readMaybe line used as String -> Maybe Int , since type inference notices that you are using return x , and therefore x must be Int .

In your first function, you don’t use Maybe , you just want to check if read succeeded. However, since you did not specify a type (neither explicit nor implicit with the output type), the type variable is ambiguous:

 _ -> case readMaybe line of 

It’s easy to fix there: annotate type:

 _ -> case readMaybe line :: Maybe Int of 

By the way, this is exactly the same behavior that you encounter when using read in ghci without any type context:

  > read "1234"
 <interactive>: 10: 1:
 No instance for (Read a0) arising from a use of `read '
 The type variable `a0 'is ambiguous

Once you clear the type, everything is fine:

  > read "1234" :: Int
 1234

Clarification of things

Now that we have seen why the error occurred, make this program much easier. First of all, we will use custom readMaybe :

 readMaybeInt :: String -> Maybe Int readMaybeInt = readMaybe 

Now how to count numbers? Numbers are those words where readMaybeInt does not return Nothing :

 countNumbers :: String -> Int countNumbers = length . filter isJust . map readMaybeInt . words 

How to calculate numbers in standard input now? We simply enter the input until one line is completely empty, match countNumbers with all these lines, and then sum :

 lineNumberCount :: IO Int lineNumberCount = getContents >>= return . sum . map countNumbers . takeWhile (/= "") . lines 

If you do not use binding methods, this is basically

 lineNumberCount :: IO Int lineNumberCount = do input <- getContents return . sum . map countNumbers . takeWhile (/= "") . lines $ input 

In general, we get the following short solution:

 import Control.Monad (liftM) import Data.Maybe (isJust) import Text.Read (readMaybe) readMaybeInt :: String -> Maybe Int readMaybeInt = readMaybe countNumbers :: String -> Int countNumbers = length . filter isJust . map readMaybeInt . words lineNumberCount :: IO Int lineNumberCount = getContents >>= return . sum . map countNumbers . takeWhile (/= "") . lines 

Now in the IO-monad there is only one function, and all functions are basically applications of standard functions. Note that getContents will close the standard input descriptor. If you want to use, you better use something like

 input :: String -> IO [String] input delim = do ln <- getLine if ln == delim then return [] else input delim >>= return . (ln:) 

which will retrieve rows until a row matching delim . Note that you need to change lineNumberCount in this case:

 lineNumberCount :: IO Int lineNumberCount = input "" >>= return . sum . map countNumbers 
+9
source share

All Articles