Building a Better Generic Parser

ok, here's a follow-up to my previous question (in general) assembling parsers from custom datatypes? ... I took the advice and decided to build my parsers with generic juice and everything is fine so far.

I need to expand my implementation a bit to handle more complex situations. Namely, consider these two definitions data

where B

built on top A

:

data A = A String Int

data B = B A Double

      

In order to analyze all structures in general data

, I define the following class

:

class HasSimpleParser f where
  getSimpleParser :: Parser f

class Parsable f where
  getParser :: Parser f

class GenericallyParsable f where
  getGenericParser :: Parser f

      

Primitive types like Int, String, Double

etc. can easily be instantiated HasSimpleParser

. Then I create a data structure like an A

instance Parsable

by doing

instance (Generic r, Code r ~ '[xs], All HasSimpleParser xs) => Parsable r where
  getParser = to . SOP. Z <$> hsequence (hcpure (Proxy @HasSimpleParser) getSimpleParser)

      

I present a class GenericallyParsable

for parsing data structure such as B

. So I am doing the following:

 instance (Generic r, Code r ~ '[xs], All Parsable xs) => GenericallyParsable r where
  getGenericParser = to . SOP. Z <$> hsequence (hcpure (Proxy @Parsable) getParser)

      

The final pieces of the puzzle are the parsing functions:

parseA :: InputStream ByteString -> IO A
parseA = parseFromStream (getGenericParser @A)

parseB :: InputStream ByteString -> IO B
parseB = parseFromStream (getGenericParser @B)

      

However, the code won't compile and I got the following error:

• Couldn't match type ‘'['[Char, [Char]]]’ with ‘'[]’
    arising from a use of ‘getGenericParser’
• In the first argument of ‘parseFromStream’, namely
    ‘(getGenericParser @A)’
  In the expression: parseFromStream (getGenericParser @A)
  In an equation for ‘parseA’:
      parseA = parseFromStream (getGenericParser @A)

      

So how do I change the code to work?

+3


source to share


1 answer


I think the class GenericallyParsable

is unnecessary.

Just define an instance HasSimpleParser

for A

that contacts Parsable

:

instance HasSimpleParser A where
    getSimpleParser = getParser 

      

If you end up declaring many instances of this type for your records, you can simplify it a bit with {-# language DefaultSignatures #-}

and change the definition of HasSimpleParser

to

class HasSimpleParser c where
    getSimpleParser :: Parser c
    default getSimpleParser :: Parsable c => Parser c
    getSimpleParser = getParser

      



Now, in the record instances, you only need to write:

instance HasSimpleParser A

      


In fact, perhaps even a distinction between HasSimpleParser

and Parsable

is unnecessary. A single HasParser

typeclass with instances for both base and composite types will suffice. The default implementation uses generics-sop and requires a restriction (Generic r, Code r ~ '[xs], All HasParser xs)

.

class HasParser c where
    getParser :: Parser c
    default getParser :: (Generic c, Code c ~ '[xs], All HasParser xs) => Parser c
    getParser = ...generics-sop code here...

      

+2


source







All Articles