Proverò a dimostrare che questo può essere fatto per GADT specifici, utilizzando il GADT come esempio.
Utilizzerò il pacchetto Data.Reify. Ciò mi impone di definire una nuova struttura dati in cui le posizioni recusive sono sostituite da un parametro.
data AstNode s where
IntLitN :: Int -> AstNode s
AddN :: s -> s -> AstNode s
BoolLitN :: Bool -> AstNode s
IfThenElseN :: TypeRep -> s -> s -> s -> AstNode s
Nota che rimuovo un sacco di informazioni tipo che era disponibile nella GADT originale. Per i primi tre costruttori è chiaro quale fosse il tipo associato (Int, Int e Bool). Per l'ultimo, ricorderò il tipo utilizzando TypeRep (disponibile in Data.Typeable). L'istanza per MuRef, richiesta dal pacchetto reify, è mostrata di seguito.
instance Typeable e => MuRef (Ast e) where
type DeRef (Ast e) = AstNode
mapDeRef f (IntLit a) = pure $ IntLitN a
mapDeRef f (Add a b) = AddN <$> f a <*> f b
mapDeRef f (BoolLit a) = pure $ BoolLitN a
mapDeRef f (IfThenElse a b c :: Ast e) =
IfThenElseN (typeOf (undefined::e)) <$> f a <*> f b <*> f c
Ora possiamo usare reifyGraph per recuperare la condivisione. Tuttavia, molte informazioni sul tipo sono andate perse. Cerchiamo di recuperarlo.Ho modificato la sua definizione di AST2 leggermente:
data Ast2 e where
IntLit2 :: Int -> Ast2 Int
Add2 :: Unique -> Unique -> Ast2 Int
BoolLit2 :: Bool -> Ast2 Bool
IfThenElse2 :: Unique -> Unique -> Unique -> Ast2 e
Il grafico dal pacchetto reificare assomiglia a questo (dove e = AstNode):
data Graph e = Graph [(Unique, e Unique)] Unique
Consente di creare una nuova struttura di dati grafico in cui possiamo memorizzare Ast2 Int e Ast2 Bool separatamente (quindi, se le informazioni sul tipo sono state recuperate):
data Graph2 = Graph2 [(Unique, Ast2 Int)] [(Unique, Ast2 Bool)] Unique
deriving Show
Ora abbiamo solo bisogno di trovare una funzione da Graph AstNode (il risultato di reifyGraph) per Graph2:
recoverTypes :: Graph AstNode -> Graph2
recoverTypes (Graph xs x) = Graph2 (catMaybes $ map (f toAst2Int) xs)
(catMaybes $ map (f toAst2Bool) xs) x where
f g (u,an) = do a2 <- g an
return (u,a2)
toAst2Int (IntLitN a) = Just $ IntLit2 a
toAst2Int (AddN a b) = Just $ Add2 a b
toAst2Int (IfThenElseN t a b c) | t == typeOf (undefined :: Int)
= Just $ IfThenElse2 a b c
toAst2Int _ = Nothing
toAst2Bool (BoolLitN a) = Just $ BoolLit2 a
toAst2Bool (IfThenElseN t a b c) | t == typeOf (undefined :: Bool)
= Just $ IfThenElse2 a b c
toAst2Bool _ = Nothing
Consente di fare un esempio:
expr = Add (IntLit 42) expr
test = do
graph <- reifyGraph expr
print graph
print $ recoverTypes graph
stampe:
let [(1,AddN 2 1),(2,IntLitN 42)] in 1
Graph2 [(1,Add2 2 1),(2,IntLit2 42)] [] 1
La prima riga ci mostra che reifyGraph ha recuperato in modo corretto la condivisione. La seconda riga ci mostra che sono stati trovati solo i tipi Ast2 Int (che è anche corretto).
Questo metodo è facilmente adattabile per altri GADTs specifici, ma non vedo come potrebbe essere realizzato interamente generico.
Il codice completo è disponibile all'indirizzo http://pastebin.com/FwQNMDbs.
ho fatto il mio esempio un po 'difficile da seguire utilizzando gli stessi nomi per i costruttori di entrambi i tipi di dati. Li ho ribattezzati per avere più senso. Sembra che tu l'abbia già fatto nel tuo codice. – tibbe
@ Sjoerd-visscher Credo che la soluzione (almeno quella che usa 'Typeable') abbia un piccolo problema: ostacola l'analisi della condivisione. Non so se questo è dovuto al costruttore di Wrap extra o a qualcos'altro. Comunque i miei soldi sono sul costruttore di Wrap. Qualche idea? –
@AlessandroVermeulen Francamente non so nulla sulla condivisione. Chi/cosa fa l'analisi della condivisione? –