Spróbuję pokazać, że można to zrobić dla konkretnych GADT, używając swojego GADT jako przykładu.
Będę używał pakietu Data.Reify. Wymaga to zdefiniowania nowej struktury danych, w której pozycje rekurencyjne są zastępowane przez parametr.
data AstNode s where
IntLitN :: Int -> AstNode s
AddN :: s -> s -> AstNode s
BoolLitN :: Bool -> AstNode s
IfThenElseN :: TypeRep -> s -> s -> s -> AstNode s
Należy pamiętać, że usuwam wiele informacji o typie, które były dostępne w oryginalnym GADT. Dla pierwszych trzech konstruktorów jest jasne, jaki jest powiązany typ (Int, Int i Bool). Dla ostatniego zapamiętuję typ używając TypeRep (dostępny w Data.Typeable). Wystąpienie dla MuRef, wymagane przez pakiet reify, pokazano poniżej.
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
Teraz możemy użyć reifyGraph odzyskać udostępnianie. Jednak wiele informacji o typie zostało utraconych. Spróbujmy go odzyskać.I zmienił swoją definicję AST2 nieznacznie:
data Ast2 e where
IntLit2 :: Int -> Ast2 Int
Add2 :: Unique -> Unique -> Ast2 Int
BoolLit2 :: Bool -> Ast2 Bool
IfThenElse2 :: Unique -> Unique -> Unique -> Ast2 e
wykres z pakietu zreifikować wygląda następująco (gdzie e = AstNode):
data Graph e = Graph [(Unique, e Unique)] Unique
Pozwala tworzyć nowe struktury danych wykresu gdzie możemy przechowywać Ast2 Int i Ast2 Bool osobno (stąd, gdy informacja o typie została odzyskana):
data Graph2 = Graph2 [(Unique, Ast2 Int)] [(Unique, Ast2 Bool)] Unique
deriving Show
Teraz musimy tylko znaleźć funkcję od Graph AstNode (w wyniku reifyGraph) do 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
Zróbmy przykład:
expr = Add (IntLit 42) expr
test = do
graph <- reifyGraph expr
print graph
print $ recoverTypes graph
Wydruki:
let [(1,AddN 2 1),(2,IntLitN 42)] in 1
Graph2 [(1,Add2 2 1),(2,IntLit2 42)] [] 1
Pierwsza linia pokazuje, że reifyGraph poprawnie odzyskała udostępnianie. Druga linia pokazuje nam, że znaleziono tylko typy Ast2 Int (co również jest poprawne).
Ta metoda jest łatwa do dostosowania do innych specyficznych GADT, ale nie widzę, jak można ją w pełni wygenerować.
Kompletny kod można znaleźć pod adresem http://pastebin.com/FwQNMDbs.
Zrobiłem mój przykład nieco trudny do naśladowania, używając tych samych nazw dla konstruktorów obu typów danych. Zmieniłem ich nazwę, by miały więcej sensu. Wygląda na to, że już to zrobiłeś w swoim kodzie. – tibbe
@ Sjoerd-visscher Wierzę, że rozwiązanie (przynajmniej takie, które używa '' Typeable') ma jeden mały problem: utrudnia analizę udostępniania. Nie wiem, czy jest to spowodowane dodatkowym konstruktorem Wrap, czy z powodu czegoś innego. Jednak moje pieniądze są na konstruktorze Wrap. Jakieś pomysły? –
@AlessandroVermeulen Szczerze mówiąc nic nie wiem o dzieleniu się. Kto/co analizuje udostępnianie? –