如何在IO中正确使用readMaybe函数

时间:2013-12-29 22:55:10

标签: haskell

我大约4个月前开始在Haskell编程,现在我已经到了必须处理Haskell的IO系统的地步。 我已经做了很多IO操作,并且没有遇到任何我自己无法解决的问题,但这次我用Google搜索了将近两个小时无法获得有关函数readMaybe的一些信息。所以我有以下问题设置解决,我已经尝试了很多不同的方法来解决它,但我一直从编译器得到相同的失败消息:

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

我理解编译器想要告诉我的内容,但我不知道如何解决这个问题。我已经尝试添加类约束,但没有成功。 所以这是我非常小而简单的程序,它只计算用户输入的有效数字的数量。该程序旨在当用户输入空行时终止。 这只是我以后想要用于项目的辅助功能。

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

不幸的是我找不到关于函数readMaybe的很多信息。我唯一能找到的是Haskell库Text.Read:

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

对我来说非常奇怪的是我已经编写了这样一个使用readMaybe函数的函数,它完美地工作了...... 该程序只是询问用户一个号码,并且只要用户输入有效号码

就会一直询问
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

据我所知,两个程序中readMaybe函数的使用没有区别,因此它在一个程序中起作用,但在另一个程序中起作用却没有:)

我会非常感谢你的任何暗示!!

2 个答案:

答案 0 :(得分:14)

这与IO无关,所以也许你不明白编译器试图告诉你什么。 a的签名中有一个类型变量readMaybe; a必须有Read个实例,但除此之外,它可以是任何内容。编译器告诉您它没有任何方法可以确定您想要的a

getLineInt中,您没有遇到此问题,因为您返回readMaybe的结果,类型签名表明它应该是Int。在countNumbers中,您没有使用readMaybe的结果,因此没有任何内容可用于确定正确的类型。您可以通过添加显式类型签名来解决此问题(我选择Int,因为您显然是在计算数字):

_ -> case readMaybe line :: Maybe Int of

最后关于do符号的一句话:它只是语法糖,你不必一直使用它。您可以简单地编写do return x而不是

来代替return x
x <- getLineInt
return x

你可以简单地做

getLineInt

这使事情更具可读性:

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

答案 1 :(得分:9)

为什么会这样?

在您的第二个函数中,readMaybe line显然被用作String -> Maybe Int,因为类型推断会注意到您使用return x,因此x必须是Int 1}}。

在你的第一个函数中,你根本不使用Maybe的值,你只想检查read是否成功。但是,由于您没有指定类型(类型推断既不显式也不隐式),因此类型变量不明确:

_  -> case readMaybe line of

有一个简单的解决方法:注释类型:

_  -> case readMaybe line :: Maybe Int of

顺便说一句,这与您在read中使用ghci而没有任何类型上下文时遇到的行为完全相同:

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

一旦你明确了类型,一切都很好:

> read "1234" :: Int
1234

清楚明白

现在我们已经看到了错误发生的原因,让我们让这个程序更简单。首先,我们将使用自定义readMaybe

readMaybeInt :: String -> Maybe Int
readMaybeInt = readMaybe

现在如何计算数字?数字是readMaybeInt不返回Nothing的那些字:

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

现在如何计算标准输入中的数字?我们只需输入输入,直到一行完全为空,在所有这些行上映射countNumbers,然后sum

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

如果您不习惯绑定方法,那基本上是

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

总而言之,我们得到以下简洁的解决方案:

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

现在只有一个功能在IO monad中工作,所有功能基本上都是标准功能的应用。请注意,getContents将关闭标准输入的句柄。如果你想使用你更喜欢使用像

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

将提取行,直到找到匹配delim的行。请注意,在这种情况下您需要更改lineNumberCount

lineNumberCount :: IO Int
lineNumberCount = 
  input "" >>= return . sum . map countNumbers