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?
source to share
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...
source to share