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

tzw*_*ckl 7 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
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

不幸的是,我找不到关于函数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.
Run Code Online (Sandbox Code Playgroud)

对我来说非常奇怪的是我已经编写了这样一个使用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
Run Code Online (Sandbox Code Playgroud)

到目前为止,我看到两个程序中函数readMaybe的使用没有区别,因此它在一个程序中工作,但在另一个程序中不起作用:)

我真的很感谢你的任何暗示!

ray*_*nad 14

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

getLineInt你没有这个问题,因为你返回的结果readMaybe和类型签名说它应该是Int.在countNumbers,你没有使用结果readMaybe,所以没有什么可以用来确定正确的类型.您可以通过添加显式类型签名来解决此问题(我选择了Int因为您显然是在计算数字):

_ -> case readMaybe line :: Maybe Int of
Run Code Online (Sandbox Code Playgroud)

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

x <- getLineInt
return x
Run Code Online (Sandbox Code Playgroud)

你可以干脆做

getLineInt
Run Code Online (Sandbox Code Playgroud)

这使事情更具可读性:

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
Run Code Online (Sandbox Code Playgroud)


Zet*_*eta 9

为什么会这样?

在你的第二个函数中,很明显它readMaybe line被用作String -> Maybe Int,因为类型推断通知你使用return x,因此x必须是Int.

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

_  -> case readMaybe line of
Run Code Online (Sandbox Code Playgroud)

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

_  -> case readMaybe line :: Maybe Int of
Run Code Online (Sandbox Code Playgroud)

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

> 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
Run Code Online (Sandbox Code Playgroud)

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

countNumbers :: String -> Int
countNumbers = length . filter isJust . map readMaybeInt . words
Run Code Online (Sandbox Code Playgroud)

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

lineNumberCount :: IO Int
lineNumberCount = 
  getContents >>= return . sum . map countNumbers . takeWhile (/= "") . lines
Run Code Online (Sandbox Code Playgroud)

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

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

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

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
Run Code Online (Sandbox Code Playgroud)

现在IO monad中只有一个功能,所有功能基本上都是标准功能的应用.请注意,getContents将关闭标准输入的句柄.如果你想使用你更好的使用类似的东西

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

它将提取线条,直到delim找到线条匹配.请注意,lineNumberCount在这种情况下您需要更改:

lineNumberCount :: IO Int
lineNumberCount = 
  input "" >>= return . sum . map countNumbers
Run Code Online (Sandbox Code Playgroud)