Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Build more advanced generic parser

ok, here is a continuation of my previous question (Generically) Build Parsers from custom data types?. I took the advice and decided to build my parsers with generic-sop and all was going fine until now.

I need to expand my implementation a little bit so more complicated situations can be dealt with. Namely, consider these two data definitions, where B is built on top of A:

data A = A String Int

data B = B A Double

In order to generically parse all data structures, 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

The primitive types such as Int, String, Double etc can be made into instances of HasSimpleParser easily. Then I make data structure such as A an instance of 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 introduce the class GenericallyParsable to parse data structure like B. So I do the following:

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

The last 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 should I modify the code to work?

like image 803
user2812201 Avatar asked Feb 10 '26 15:02

user2812201


1 Answers

I think the GenericallyParsable typeclass is not necessary.

Just define a HasSimpleParser instance for A that piggybacks on Parsable:

instance HasSimpleParser A where
    getSimpleParser = getParser 

If you end up declaring many instances of this type for your records, you can streamline it a little using {-# language DefaultSignatures #-} and changing 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 would only have to write:

instance HasSimpleParser A

In fact, perhaps even the distinction between HasSimpleParser and Parsable in unnecessary. A single HasParser typeclass with instances for both basic and composite types would be enough. The default implementation would use generics-sop and require a (Generic r, Code r ~ '[xs], All HasParser xs) constraint.

class HasParser c where
    getParser :: Parser c
    default getParser :: (Generic c, Code c ~ '[xs], All HasParser xs) => Parser c
    getParser = ...generics-sop code here...
like image 82
danidiaz Avatar answered Feb 15 '26 13:02

danidiaz



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!