Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to properly use the readMaybe function in IO

Tags:

haskell

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!!

like image 755
tzwickl Avatar asked Dec 29 '13 22:12

tzwickl


2 Answers

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
like image 105
raymonad Avatar answered Sep 22 '22 09:09

raymonad


Why does this happen?

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

Making things clear

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
like image 41
Zeta Avatar answered Sep 21 '22 09:09

Zeta