为GADT派生读取实例

时间:2016-11-08 22:04:50

标签: haskell template-haskell

如何为此GADT自动派生Read个实例:

{-# 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

这个想法是Bar是一个可序列化的类型。

我可以通过以下代码段或Parsec编写Read实例,但考虑到我在不同模块中有许多类似于TypeDecBar的类型,这是一个样板:< / p>

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 (== ' ')

1 个答案:

答案 0 :(得分:5)

我不知道通用的解决方案。也就是说,通过定义Read方法来编写readPrec个实例要容易得多(有关一些额外函数,请参阅Text.ReadText.ParserCombinators.ReadPrec,也许Text.ParserCombinators.ReadP甚至更多)而不是使用Parsec或定义readsPrec。这不是你写的绝对最快,但它应该相当快。

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

如果TypeDec更复杂,并且您有ReadTypeDec Int TypeDec String个实例(使用FlexibleInstances或辅助类),那么您''我可能想要更模块化的东西:

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

请注意,在第二个示例中,GHC无法自行确定替代方案应具有的类型,但是当我们修复第二个字段的类型时,推理会将其传播到第一个字段。因此,在第一种方案中,我们只会查找"TypeDecInt",而在第二种方法中,我们只会查找"TypeDecString"

dependent-sum包定义了一种推广Bar

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

它还使用一些额外的类为Read定义DSum个实例。一般方法看起来很稳固,但我建议两个更改。第一种是使用ReadPrec而不是与列表混淆。第二种是用以下(更简单的)存在性类型替换更高级别的GReadResult类型:

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

当然,这些变化会迫使你自己实施,但没关系。