2

ここで提供されているHaskell Koansを試してみました: https://github.com/roman/HaskellKoans

カスタム代数データ型の解析を含む最後の 2 つの公案に行き詰まっています。最初は次のとおりです。

data Atom = AInt Int | ASym Text deriving (Eq, Show)

testAtomParser :: Test
testAtomParser = testCase "atom parser" $ do
    -- Change parser with the correct parser to use
    --
    let parser = <PARSER HERE> :: P.Parser Atom
    assertParse (ASym "ab") $ P.parseOnly parser "ab"
    assertParse (ASym "a/b") $ P.parseOnly parser "a/b"
    assertParse (ASym "a/b") $ P.parseOnly parser "a/b c"
    assertParse (AInt 54321) $ P.parseOnly parser "54321"

Atom代数データ型を解析してアサーションを渡すことができるように、変数パーサーをどのように定義できますか?

4

1 に答える 1

9

私。

ADT のパーサーは、ADT の形状を反映する傾向があります。ADT は 2 つのバラバラな部分で構成されているため、パーサーにも 2 つのバラバラな部分がある可能性があります。

atom = _ <|> _

Ⅱ.

1 桁を解析する方法を知っていると仮定すると (基本パーサーと呼びましょうdigit)、(非負の) 整数を繰り返すだけで解析します。

natural = let loop = digit >> loop in loop

これにより、数字の無限ストリームが正常に解析され、破棄されます。もっとうまくやれるでしょうか?残念ながら、モナドインスタンスだけではなく、別の基本的なコンビネータ が必要ですmany。これは、他のパーサーを変更して入力を 0 回以上消費し、結果をリストに蓄積します。空の解析は有効な数値ではないため、実際にはこれを少し調整します

many1 p = do x  <- p
             xs <- many p
             return (x:xs)

natural' = many1 digit

III.

原子はどうですか?テスト ケースに合格するには、アトムが 1 対多の英数字またはバックスラッシュである必要があるようです。繰り返しになりますが、このバラバラな構造は、パーサーですぐに表現できます。

sym = many1 (_ <|> _)

ここでも組み込みの単純なパーサー コンビネータを使用して、必要なものを構築します。たとえば、satisfy :: (Char -> Bool) -> Parser Char述語を満たす任意の文字に一致するものです。すぐに別の便利なコンビネータを構築でき、char c = satisfy (==c) :: Char -> Parser Charそれで完了です。

sym = many1 (char '/' <|> satisfy isAlpha)

whereisAlphaは正規表現によく似た述語[a-zA-Z]です。

IV.

これで、パーサーのコアができました

natural <|> sym :: Parser String

コンビネータはmany1、文字パーサーを文字のリスト( Strings!) のパーサーに持ち上げます。このリフティング アクションは、ADT パーサーを構築するための基本的な考え方でもあります。Parser Stringに持ち上げたいと思いParser Atomます。それを行う1つの方法は、関数を使用することtoAtom :: String -> AtomですfmapParser

atom' :: Parser Atom
atom' = fmap toAtom (natural <|> sym)

しかし、型を持つ関数はString -> Atom、そもそもパーサーを構築するという目的を無効にします。

I. で述べたように、重要な部分は、ADT の形状がatomパーサーの形状に反映されることです。これを利用して、最終的なパーサーを構築する必要があります。

V.

atomパーサーの構造で情報を利用する必要があります。代わりに 2 つの関数を作成しましょう

liftInt :: String -> Atom  -- creates `AInt`s
liftSym :: String -> Atom  -- creates `ASym`s

liftInt = AInt . read
liftSym = ASym

Stringそれぞれがs を s に変換する方法を述べているだけでなく、どのようなものを扱っているAtomかを宣言しています。に解析できない文字列を渡すと、実行時エラーがスローされることに注意してください。幸いなことに、それはまさに私たちが知っていることです。AtomliftIntInt

atomInt :: Parser Atom
atomInt = liftInt <$> natural

atomSym :: Parser Sym
atomSym = liftSym <$> sym

atom'' = atomInt <|> atomSym

ここで、私たちのパーサーは、自然に有効な解析である文字列のみを返すatom''という保証を利用します--- への呼び出しは失敗しません!--- そして、両方を順番に構築しようとします。私たちの ADT の構造のような構造。naturalreadAIntASym

Ⅵ.

したがって、シバン全体は

atom''' =     AInt . read <$> many1 digit
          <|> ASym <$> many1 (    char '/' 
                              <|> satisfy isAlpha)

これは、パーサー コンビネータの面白さを示しています。全体は、小さくて構成可能なシンプルな部品を使用して、地面から構築されます。それぞれが非常に小さな仕事をしますが、全体としてパーサーの大きなスペースにまたがっています。

また、この文法は、ADT のブランチを増やしたり、より完全に指定されたシンボル タイプ パーサーを使用したり、失敗の装飾を<?>使用して簡単に拡張したりできます。これにより、失敗した解析で優れたエラー メッセージが表示されます。

于 2013-02-01T22:40:51.057 に答える