3

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?

Community
  • 1
  • 1
user2812201
  • 437
  • 3
  • 7

1 Answers1

2

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...
danidiaz
  • 26,936
  • 4
  • 45
  • 95
  • Gist with full example: https://gist.github.com/danidiaz/2a2ddb5dc120718012eced28200b56d5 – danidiaz May 08 '17 at 21:36
  • thanks a bunch. Let me play with the gist a little bit and come back to you if I end up with more questions! – user2812201 May 08 '17 at 23:06
  • Works like a charm! Thank you very much. Though there is another tiny complication that needs to be taken care of: What about lists in the record? For example, `data C = Bool [Int]`? And possibly even more complicated: `data D = [C] Double`? I guess I need sth. like `instance (HasParser c) => HasParser [c]` but I'm not able to come up with the implementation. – user2812201 May 09 '17 at 15:14
  • @user2812201 It depends on the type of your parser and on how lists are serialized (extra `\NUL`?). Does your parser have an `Alternative` instance? If so, perhaps you could write something like megaparsec's `manyTill :: Alternative m => m a -> m end -> m [a]` – danidiaz May 09 '17 at 20:39
  • my `String`, `Bool`, `Double` etc all end with a '\NUL'. Is it possible to use `manyTill` or `many` combinators? – user2812201 May 09 '17 at 21:10
  • @user2812201 On the wire, what differentiaties a list of two ints from a register with two int fields? – danidiaz May 09 '17 at 21:12
  • Here is a little backgroud: I need to parse a stream of responses from the server. In the stream, every response is preceded by an id, which is an integer. I just parse the id first and then call the corresponding parser. So I guess the answer to your question is nothing differentiates the two responses, and I have to keep track by hand. – user2812201 May 09 '17 at 21:18