Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deriving Read Instances for GADTs

How can I automatically derive a Read instance for this GADTs:

{-# LANGUAGE GADTs, StandaloneDeriving #-}

data TypeDec a where
  TypeDecInt    :: TypeDec Int
  TypeDecString :: TypeDec String

deriving instance Show (TypeDec a)

data Bar where
  Bar :: (Show a, Read a) => TypeDec a -> a -> Bar

deriving instance Show Bar

The idea is that Bar is a serializable type.

I can write a Read instance by the following snippet or by Parsec, but considering that I have many similar types to TypeDec and Bar in different modules this is a boilerplate:

instance Read Bar where
  readsPrec _ s =
    let (bar, tup) = second breaks . breaks $ s
    in if "Bar" == bar then uncurry parse tup else []
    where
      parse :: String -> String -> [(Bar, String)]
      parse tdec = case tdec of
        "TypeDecInt"    -> parse' TypeDecInt
        "TypeDecString" -> parse' TypeDecString
        _               -> const []

      parse' :: (Show a, Read a) => TypeDec a -> String -> [(Bar, String)]
      parse' tdec s = [(Bar tdec (read s), "")]

      breaks :: String -> (String, String)
      breaks = second (drop 1) . break (== ' ')
like image 628
homam Avatar asked Nov 08 '16 22:11

homam


1 Answers

I don't know of a generic solution. That said, it's a lot easier to write Read instances by defining the readPrec method (see Text.Read especially, Text.ParserCombinators.ReadPrec for some extra functions, and maybe Text.ParserCombinators.ReadP for even more) instead of using Parsec or defining readsPrec. This isn't the absolute fastest you could write, but it should be reasonably fast.

import Text.Read

instance Read Bar where
  readListPrec = readListPrecDefault

  readPrec = parens $ do
    Ident "Bar" <- lexP
    Ident td <- parens lexP
    case td of
      "TypeDecInt" -> Bar TypeDecInt <$> readPrec
      "TypeDecString" -> Bar TypeDecString <$> readPrec
      _ -> empty

If TypeDec is more complicated, and you have Read instances for TypeDec Int and TypeDec String (using FlexibleInstances or an auxiliary class), then you'll likely want something more modular:

instance Read Bar where
  readListPrec = readListPrecDefault
  readPrec = parens $ do
    Ident "Bar" <- lexP
    (Bar <$> readPrec <*> (readPrec :: ReadPrec Int))
       <|> (Bar <$> readPrec <*> (readPrec :: ReadPrec String))

Note that in the second example, GHC can't figure out by itself what types the alternatives should have, but when we fix the type of the second field, inference propagates that to the first field. So in the first alternative, we'll only look for "TypeDecInt" and in the second one we'll only look for "TypeDecString".


The dependent-sum package defines a type that generalizes your Bar.

data DSum tag f = forall a . !(tag a) :=> f a

It also defines a Read instance for DSum using some extra classes. The general approach looks solid, but I'd recommend two changes. The first is to use ReadPrec instead of fussing with lists. The second is to replace the higher-rank GReadResult type with the following (much simpler) existential one:

data GReadResult t = forall a . GReadResult (t a)

Of course, these changes will force you to implement it yourself, but that's okay.

like image 83
dfeuer Avatar answered Sep 20 '22 10:09

dfeuer