I started with programming in Haskell about 4 month ago and now I came to the point where I have to deal with the IO system of Haskell. I already did a lot of IO actions and haven't faced any problems I couldn't solve by myself, but this time I googled for almost two hours for no avail to get some information about the function readMaybe. So I have the following problem set to solve and I already tried a lot of different approaches to solve it but all the time I get the same failure 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 does want to tell me but I have no idea how to solve this problem. I already tried to add a class constraint, but without success. So here is my very small and simple program that is just counting how many valid numbers the user has entered. The program is meant to terminate when the user enters an empty line. This is just a auxiliary function I want to use for my project later on.
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 couldn't find out a lot of informations about the function readMaybe. The only thing I could find was in the Haskell library Text.Read:
readMaybe :: Read a => String -> Maybe aSource
Parse a string using the Read instance. Succeeds if there is exactly one valid result.
The very weird thing for me is that I have already written such a function that uses the readMaybe function and it worked perfectly ... This program is just asking the user for a number and keeps asking as long as 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
So far as I can see there are no differences between the usage of the function readMaybe in the two programs and therefore it works in the one but not in the other :)
I would be really thankful for any hints from you!!
This has nothing to do with IO, so maybe you don't understand what the compiler is trying to tell you. There is a type variable a
in readMaybe
's signature; a
has to have a Read
instance, but other than that it can be anything. The compiler is telling you that it doesn't have any way to determine what you want a
to be.
In getLineInt
you don't have this problem, because you are returning the result of readMaybe
and the type signature says it should be Int
. In countNumbers
, you're not using the result of readMaybe
, so there's nothing that can be used to determine the correct type. You can fix this by adding an explicit type signature (I picked Int
since you're apparently counting numbers):
_ -> case readMaybe line :: Maybe Int of
Finally a word about do
notation: 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 of
x <- getLineInt
return x
you can simply do
getLineInt
That makes things more readable:
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
In your second function, it is clear that readMaybe line
is used as String -> Maybe Int
, since type inference notices that you use return x
and therefore x
must be an Int
.
In your first function, you don't use the Maybe
's value at all, you just want to check whether the read
succeeded. However, since you didn't specify the type (neither explicit nor implicit with type inference), the type variable is ambiguous:
_ -> case readMaybe line of
There's an easy fix: annotate the type:
_ -> case readMaybe line :: Maybe Int of
By the way, this is exactly the same behaviour you encounter when you use 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
As soon as you make the type clear everything is fine:
> read "1234" :: Int 1234
Now that we've seen why the error happens, lets make this program much simpler. First of all, we're going to use a custom readMaybe
:
readMaybeInt :: String -> Maybe Int
readMaybeInt = readMaybe
Now how does one count numbers? Numbers are those words, where readMaybeInt
doesn't return Nothing
:
countNumbers :: String -> Int
countNumbers = length . filter isJust . map readMaybeInt . words
How does one now calculate the numbers in the standard input? We simply take input until one line is completely empty, map countNumbers
on all those lines and then sum
:
lineNumberCount :: IO Int
lineNumberCount =
getContents >>= return . sum . map countNumbers . takeWhile (/= "") . lines
If you're not used to the bind methods, that's basically
lineNumberCount :: IO Int
lineNumberCount = do
input <- getContents
return . sum . map countNumbers . takeWhile (/= "") . lines $ input
All in all we get the following terse 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 there's only one function working in the IO monad, and all functions are basically applications of standard functions. Note that getContents
will close the handle to the standard input. If you want to use you're better of using something like
input :: String -> IO [String]
input delim = do
ln <- getLine
if ln == delim then return []
else input delim >>= return . (ln:)
which will extract lines until a line matching delim
has been found. Note that you need to change lineNumberCount
in this case:
lineNumberCount :: IO Int
lineNumberCount =
input "" >>= return . sum . map countNumbers
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With