1

私はまだ Haskell と関数型プログラミング全般にかなり慣れていないので、基本的な概念を学習する手段として、JSON を解析し、きれいに出力する Parsec を使用した小さなプログラムを作成しています。これは私がこれまでに持っているものです:

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

今のところ、数値は整数であると想定しています。個別parseJStringparseJNumber、、、、parseJBoolおよびparseJNullghci で期待どおりに動作します。さらに、parseJValue文字列と数値を正しく解析します。

ghci> parse parseJString "test" "\"test input\""
Right (JString "test input")
ghci> parse parseJNumber "test" "345"
Right (JNumber 345.0)
ghci> parse parseJBool "test" "true"
Right (JBool True)
ghci> parse parseJNull "test" "null"
Right JNull
ghci> parse parseJValue "test" "\"jvalue test\""
Right (JString "jvalue test")
ghci> parse parseJValue "test" "789"
Right (JNumber 789.0)

parseJValuetrueただし、 、false、またはを解析しようとすると失敗し、null興味深いエラーが発生します。

ghci> parse parseJValue "test" "true"
Right (JNumber *** Exception: Prelude.read: no parse

解析は成功しましたが、解析はJNumberPrelude.read が失敗したことを示すエラーが続きます。パーサーを構築する際の中心的な概念が欠けているように感じますが、どこが間違っているのかわかりません。また、自分のコードで初歩的な間違いを犯していますか?つまり、これは「悪い」haskell と見なされますか?

4

2 に答える 2

2

many問題はinの使い方ですparseJNumber。次の文字列の文字が消費されない場合も、有効な解析です (「多くの p がパーサー p をゼロ回以上適用する [...]」)。必要なものは次のmany1とおりです。

parseJNumber = do
  num <- many1 (oneOf "0123456789")
  return $ JNumber (read num :: Double)

編集:

どういうわけか、あなたの と の組み合わせは奇妙(.)に見えると思います。($)関数のパラメータを取り除くことができる場合は (.) を使用し ( の使用法など(>>=))、括弧を書くのが面倒な場合は ($) を使用します。関数では、適切なバインディングの優先順位を取得するparseJString必要はありません。(.)(上記のコードで同じ変換を行いました。)

parseJString = do
  str <- between (char '"') (char '"') (many (noneOf "\""))
  return $ JString str

さらに、リファクタリングによってコードの繰り返しをなくすことができますparseJBool:

parseJBool = do
  val <- string "true" <|> string "false"
  return (case val of
    "true"  -> JBool True
    "false" -> JBool False)

case-construct を (合計) ローカル関数に書き直すこともできます。

parseJBool = (string "true" <|> string "false") >>= return . toJBool
 where
  -- there are only two possible strings to pattern match
  toJBool "true" = JBool True
  toJBool _      = JBool False

(>>=)最後になりましたが、do ブロックの代わりに使用する他の関数を簡単に変換できます。

-- 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
于 2013-07-24T06:35:37.613 に答える