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 (== ' ')
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With