在Haskell中类型安全的可观察共享安迪·吉尔介绍如何恢复已经存在的Haskell的水平,在DSL共享。 他的溶液在实现数据具体化包 。 可这种方法进行修改与GADTs工作? 例如,鉴于此GADT:
data Ast e where
IntLit :: Int -> Ast Int
Add :: Ast Int -> Ast Int -> Ast Int
BoolLit :: Bool -> Ast Bool
IfThenElse :: Ast Bool -> Ast e -> Ast e -> Ast e
我想通过将上述AST以恢复共享
type Name = Unique
data Ast2 e where
IntLit2 :: Int -> Ast2 Int
Add2 :: Ast2 Int -> Ast2 Int -> Ast2 Int
BoolLit2 :: Bool -> Ast2 Bool
IfThenElse2 :: Ast2 Bool -> Ast2 e -> Ast2 e -> Ast2 e
Var :: Name -> Ast2 e
通过函数的方式
recoverSharing :: Ast -> (Map Name, Ast2 e1, Ast2 e2)
(我不知道的类型recoverSharing
。)
请注意,我不关心通过让结构引入新的绑定,但只有在恢复上的哈斯克尔水平存在的共享。 这就是为什么我有recoverSharing
返回Map
。
如果它不能为可重用的组件来完成,可以把它至少可以为特定GADT做了什么?
有趣的益智! 事实证明,你可以使用带有GADTs数据具体化。 你需要的是隐藏在一个存在的类型的包装。 的类型可以在以后通过在图案匹配来检索Type
数据类型。
data Type a where
Bool :: Type Bool
Int :: Type Int
data WrappedAst s where
Wrap :: Type e -> Ast2 e s -> WrappedAst s
instance MuRef (Ast e) where
type DeRef (Ast e) = WrappedAst
mapDeRef f e = Wrap (getType e) <$> mapDeRef' f e
where
mapDeRef' :: Applicative f => (forall b. (MuRef b, WrappedAst ~ DeRef b) => b -> f u) -> Ast e -> f (Ast2 e u)
mapDeRef' f (IntLit i) = pure $ IntLit2 i
mapDeRef' f (Add a b) = Add2 <$> (Var Int <$> f a) <*> (Var Int <$> f b)
mapDeRef' f (BoolLit b) = pure $ BoolLit2 b
mapDeRef' f (IfThenElse b t e) = IfThenElse2 <$> (Var Bool <$> f b) <*> (Var (getType t) <$> f t) <*> (Var (getType e) <$> f e)
getVar :: Map Name (WrappedAst Name) -> Type e -> Name -> Maybe (Ast2 e Name)
getVar m t n = case m ! n of Wrap t' e -> (\Refl -> e) <$> typeEq t t'
这里是整个代码: https://gist.github.com/3590197
编辑:我喜欢使用的Typeable
在其他的答案。 所以我做了一个版本,我的代码Typeable
太: https://gist.github.com/3593585 。 该代码是显著更短。 Type e ->
被换成Typeable e =>
其中也有一个缺点:我们不再知道可能的类型仅限于Int
和Bool
,这意味着必须有一个Typeable e
在约束IfThenElse
。
我会尽力证明这是可以针对特定GADTs来完成,使用GADT作为一个例子。
我将使用Data.Reify包。 这需要我来定义,其中recusive位置由参数更换了新的数据结构。
data AstNode s where
IntLitN :: Int -> AstNode s
AddN :: s -> s -> AstNode s
BoolLitN :: Bool -> AstNode s
IfThenElseN :: TypeRep -> s -> s -> s -> AstNode s
请注意,我删除了很多,在原GADT是可用的类型信息。 对于前三种构造很清楚什么关联的类型是(INT,INT和布尔)。 对于最后一个,我会记得使用TypeRep(在Data.Typeable可用)的类型。 实例为MuRef,通过具体化包所需的,如下所示。
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
现在,我们可以使用reifyGraph恢复共享。 然而,很多类型的信息丢失。 让我们试着恢复它。 我改变了你的AST2的定义略有:
data Ast2 e where
IntLit2 :: Int -> Ast2 Int
Add2 :: Unique -> Unique -> Ast2 Int
BoolLit2 :: Bool -> Ast2 Bool
IfThenElse2 :: Unique -> Unique -> Unique -> Ast2 e
从具体化包中的图形看起来像这样(其中e = AstNode):
data Graph e = Graph [(Unique, e Unique)] Unique
允许使一个新的图形数据结构,其中我们可以存储AST2 INT和AST2布尔分开(因此,这里的类型信息已被恢复):
data Graph2 = Graph2 [(Unique, Ast2 Int)] [(Unique, Ast2 Bool)] Unique
deriving Show
现在,我们只需要找到从图形AstNode(reifyGraph的结果)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
让我们做一个例子:
expr = Add (IntLit 42) expr
test = do
graph <- reifyGraph expr
print graph
print $ recoverTypes graph
打印:
let [(1,AddN 2 1),(2,IntLitN 42)] in 1
Graph2 [(1,Add2 2 1),(2,IntLit2 42)] [] 1
第一行告诉我们,reifyGraph已正确恢复共享。 第二行告诉我们,只有AST2 int型,已经发现(这也是正确的)。
这种方法对于其他具体GADTs容易适应,但我看不出它如何能进行完全通用。
完整的代码可以在这里找到http://pastebin.com/FwQNMDbs 。