我大约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函数的使用没有区别,因此它在一个程序中起作用,但在另一个程序中起作用却没有:)
我会非常感谢你的任何暗示!!
答案 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