Подтвердить что ты не робот

Как я могу восстановить совместное использование в GADT?

В "Безопасный общий доступ" в Haskell Энди Гилл показывает, как восстановить общий доступ, существовавший на уровне Haskell, в DSL. Его решение реализовано в пакете data-reify. Можно ли изменить этот подход для работы с GADT? Например, учитывая этот 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.)

Обратите внимание, что я не забочусь о введении новых привязок через конструкцию let, но только в восстановлении обмена, существовавшего на уровне Haskell. Вот почему я recoverSharing вернул a Map.

Если это невозможно сделать как многоразовый пакет, можно ли это сделать, по крайней мере, для конкретного GADT?

4b9b3361

Ответ 1

Интересная головоломка! Оказывается, вы можете использовать data-reify с помощью GADT. Вам нужна оболочка, которая скрывает тип в экзистенциальном. Тип позже может быть получен путем сопоставления шаблонов в типе данных 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, что означает, что в IfThenElse должно быть ограничение Typeable e.

Ответ 2

Я попытаюсь показать, что это можно сделать для определенных GADT, используя GADT в качестве примера.

Я буду использовать пакет Data.Reify. Это требует от меня определения новой структуры данных, в которой заменяющие позиции заменяются параметром.

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 и Bool). Для последнего я буду помнить тип, используя TypeRep (доступный в Data.Typeable). Экземпляр для MuRef, требуемый пакетом reify, показан ниже.

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

График из пакета reify выглядит следующим образом (где e = AstNode):

data Graph e = Graph [(Unique, e Unique)] Unique    

Давайте создадим новую структуру данных графа, где мы можем отдельно хранить Ast2 Int и Ast2 Bool (таким образом, когда информация о типе была восстановлена):

data Graph2 = Graph2 [(Unique, Ast2 Int)] [(Unique, Ast2 Bool)] Unique 
            deriving Show

Теперь нам нужно найти функцию Graph 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 типы (что также правильно).

Этот метод легко адаптируется для других конкретных GADT, но я не вижу, как это можно сделать полностью общим.

Полный код можно найти на http://pastebin.com/FwQNMDbs.