Json解析器,错误地将字符串解析为数字
一般来说,我对Haskell和函数式编程还是相当陌生的,所以我正在用Parsec编写一个小程序来解析JSON,并将其打印出来,作为学习基本概念的一种手段。这就是我到目前为止所做的:Json解析器,错误地将字符串解析为数字,json,haskell,parsec,Json,Haskell,Parsec,一般来说,我对Haskell和函数式编程还是相当陌生的,所以我正在用Parsec编写一个小程序来解析JSON,并将其打印出来,作为学习基本概念的一种手段。这就是我到目前为止所做的: import Text.Parsec import Text.Parsec.String data JValue = JString String | JNumber Double | JBool Bool | JNull
import Text.Parsec
import Text.Parsec.String
data JValue = JString String
| JNumber Double
| JBool Bool
| JNull
| JObject [(String, JValue)]
| JArray [JValue]
deriving (Eq, Ord, Show)
parseJString, parseJNumber, parseJBool, parseJNull :: Parser JValue
parseJString = do
str <- between (char '"') (char '"') (many (noneOf "\""))
return . JString $ str
parseJNumber = do
num <- many digit
return . JNumber . read $ num
parseJBool = do
val <- string "true" <|> string "false"
case val of
"true" -> return (JBool True)
"false" -> return (JBool False)
parseJNull = string "null" >> return JNull
parseJValue :: Parser JValue
parseJValue = parseJString
<|> parseJNumber
<|> parseJBool
<|> parseJNull
然而,当我尝试解析true
、false
或null
时,parseJValue
失败,我得到了一个有趣的错误
ghci> parse parseJValue "test" "true"
Right (JNumber *** Exception: Prelude.read: no parse
我得到了一个成功的解析,但是解析返回一个
JNumber
,后面是一个错误,表明Prelude.read失败。我觉得我在构建解析器时遗漏了一些核心概念,但我看不出哪里出了问题。此外,我的代码是否犯了初学者的错误,即这些错误是否会被视为“坏”haskell?问题是在parseJNumber
中使用了many
。当没有使用以下字符串的任何字符时,它也是一个有效的解析(“许多p应用解析器p零次或多次。[…]”)。您需要的是many1
:
parseJNumber = do
num <- many1 (oneOf "0123456789")
return $ JNumber (read num :: Double)
此外,还可以通过重构parseJBool
,消除代码重复:
parseJBool = do
val <- string "true" <|> string "false"
return (case val of
"true" -> JBool True
"false" -> JBool False)
您应该尝试使用
多个1位数
,而不是多个位数
。many
在零次出现参数时成功
比较:
ghci> parse (many digit) "test" "true"
Right ""
ghci> parse (many1 digit) "test" "true"
unexpected "t"
expecting digit
因此,在您的情况下,
parseJValue
中的parseJNumber
将成功并返回一个空字符串,然后将该字符串传递给read
。但是read”“::Double
失败。我完全同意您建议的重构更改,但是vim中的Syntastic给了我一个相当模糊的警告,每当我使用>=return
时,应该使用liftM
。有什么理由改变它,还是应该忽略它?您可能应该使用liftM f x
,它与x>>=return完全相同。f
。或者更好的是,您可以使用fmap fx
(只要有Functor
实例就可以使用)或fx
(只要有Applicative
实例就可以使用)。
parseJBool = do
val <- string "true" <|> string "false"
return (case val of
"true" -> JBool True
"false" -> JBool False)
parseJBool = (string "true" <|> string "false") >>= return . toJBool
where
-- there are only two possible strings to pattern match
toJBool "true" = JBool True
toJBool _ = JBool False
-- additionally, you do not need an extra type signature for `read`
-- the constructor `JNumber` already infers the correct type
parseJNumber =
many1 (oneOf "0123456789") >>= return . JNumber . read
parseJString =
between (char '"') (char '"') (many (noneOf "\"")) >>= return . JString
ghci> parse (many digit) "test" "true"
Right ""
ghci> parse (many1 digit) "test" "true"
unexpected "t"
expecting digit