2016-11-08 7 views
5

Как я могу автоматически вывести Read экземпляр для этого GADTs:Выводя прочитанной Инстансы для 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 

Идея заключается в том, что Bar является сериализации типа.

Я могу написать Read экземпляр, следующий фрагмент или Parsec, но учитывая, что у меня есть много подобных типов в TypeDec и Bar в различных модулях этого является шаблонный:

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

Это полностью выполнимо с использованием шаблона Haskell. Нужно просто использовать подход, упомянутый @dfeuer (т. Е. Используя 'case' вместо' GHC.Read.choose'). – Alec

+0

@Alec Вы знаете какой-либо пример/учебник для получения экземпляров 'Read' с шаблоном Haskell? – homam

+0

[Это] (https://wiki.haskell.org/Template_haskell/Instance_deriving_example) выглядит хорошо. – Alec

ответ

4

Я не знаю, общего решения. Тем не менее, это намного проще писать Read экземпляры путем определения метода readPrec (см Text.Read особенно Text.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 является более сложным, и у вас есть Read экземпляры для TypeDec 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) 

Конечно, эти изменения заставят вас осуществить это самостоятельно, но это нормально.

+0

'depend-sum' - интересная библиотека. Но из его [примеров] (https://github.com/mokus0/dependent-sum/blob/master/examples/FooGADT.hs#L72) кажется, что мне еще нужно определить экземпляры «GRead» (и написать некоторые шаблоны для каждого конструктора типа «tag»). – homam

+0

@homam, да, метод DSum просто накладывает определенную структуру на экземпляры; он не выиграет вас, если что-нибудь, если вы не хотите использовать связанные инструменты. Я просто подумал, что стоит упомянуть. – dfeuer