Аннотация Haskell AST с исправлением

Я работаю над созданием AST в Haskell. Я хочу добавить различные аннотации, такие как типы и информацию о местоположении, поэтому в итоге я использовал fixplate< /а>. Однако я не могу найти никаких примеров в Интернете, и у меня есть некоторые трудности.

Я настроил свой AST в соответствии с рекомендациями fixplate (некоторые зачеркнуты):

data ProgramF a
  = Unary a
          Operator
  | Number Int
  | Let { bindings :: [(Identifier, a)]
        , body :: a }

type Program = Mu ProgramF

Затем, чтобы добавить метку, я создал еще один тип и функцию для добавления меток на основе обхода дерева.

type LabelProgram = Attr ProgramF PLabel

labelProgram :: Program -> LabelProgram
labelProgram =
  annMap (PLabel . show . fst) . (snd . synthAccumL (\i x -> (i + 1, (i, x))) 0)

Однако помимо этого я сталкиваюсь с некоторыми проблемами. Например, я пытаюсь написать функцию, которая выполняет некоторые преобразования в AST. Поскольку для работы требуется метка, я сделал тип LabelProgram -> Program, но я думаю, что здесь что-то не так. Ниже приведен фрагмент части функции (одна из самых простых частей):

toANF :: LabelProgram -> Program
toANF (Fix (Ann label (Let {bindings, body}))) = Fix $ Let bindingANF nbody
  where
    bindingANF = map (\(i, e) -> (i, toANF e)) bindings
    nbody = toANF body

Я чувствую, что работаю здесь на неправильном уровне абстракции. Должен ли я явно сопоставляться с Fix Ann ... и возвращать Fix ... таким образом, или я неправильно использую fixplate?

Кроме того, меня беспокоит, как обобщать функции. Как я могу заставить свои функции работать для Programs, LabelPrograms и TypePrograms вообще?


comment
Attr f a — это всего лишь синоним Mu (Ann f a), так что вы можете использовать изобилие обходов, доступных в fixplate. toANF по сути является forget, но, читая ваши намерения, вы не хотите удалять ярлыки из своего AST, не так ли? hackage.haskell.org/package/ fixplate-0.1.7/docs/   -  person Regis Kuckaertz    schedule 23.04.2018


Ответы (1)


Изменить. Добавьте пример функции для ProgramFs с общими аннотациями.

Да, по крайней мере, в случае с toANF вы используете его неправильно.

В toANF обратите внимание, что ваше Let bindingANF nbody и сопутствующие определения bindingANF и nbody являются просто повторной реализацией fmap toANF для конкретного конструктора Let.

То есть, если вы получаете экземпляр Functor для своего ProgramF, то вы можете переписать свой фрагмент toANF как:

toANF :: LabelProgram -> Program
toANF (Fix (Ann label l@(Let _ _))) = Fix (fmap toANF l)

Если toANF просто удаляет метки, то это определение работает для всех конструкторов, а не только для Let, поэтому вы можете отказаться от шаблона:

toANF :: LabelProgram -> Program
toANF (Fix (Ann label l)) = Fix (fmap toANF l)

и теперь, согласно комментарию @Regis_Kuckaertz, вы только что повторно реализовали forget, который определяется как:

forget = Fix . fmap forget . unAnn . unFix

Что касается написания функций, которые обычно работают с Program, LabelProgram и т. д., я думаю, что имеет смысл писать общие функции в (одной) аннотации:

foo :: Attr ProgramF a -> Attr ProgramF a

и, если вам действительно нужно применить их к программе без аннотаций, определите:

type ProgramU = Attr ProgramF ()

где «U» в ProgramU означает «единица измерения». Очевидно, вы можете легко написать трансляторы для работы с Programs как ProgramUs, если это действительно необходимо:

toU :: Functor f => Mu f -> Attr f ()
toU = synthetise (const ())

fromU :: Functor f => Attr f () -> Mu f
fromU = forget

mapU :: (Functor f) => (Attr f () -> Attr f ()) -> Mu f -> Mu f
mapU f = fromU . f . toU

foo' :: Mu ProgramF -> Mu ProgramF
foo' = mapU foo

В качестве конкретного — хотя и глупого — примера, вот функция, которая разделяет Lets с несколькими привязками на вложенные Lets с одноэлементными привязками (и, таким образом, разрывает взаимно рекурсивные привязки в языке Program). Предполагается, что аннотация к мультипривязке Let будет скопирована в каждый из полученных синглетонов Let:

splitBindings :: Attr ProgramF a -> Attr ProgramF a
splitBindings (Fix (Ann a (Let (x:y:xs) e)))
  = Fix (Ann a (Let [x] (splitBindings (Fix (Ann a (Let (y:xs) e))))))
splitBindings (Fix e) = Fix (fmap splitBindings e)

Это можно применить к примеру Program:

testprog :: Program
testprog = Fix $ Unary (Fix $ Let [(Identifier "x", Fix $ Number 1), 
                                   (Identifier "y", Fix $ Number 2)] 
                                  (Fix $ Unary (Fix $ Number 3) NegOp))
                       NegOp

вот так:

> mapU splitBindings testprog
Fix (Unary (Fix (Let {bindings = [(Identifier "x",Fix (Number 1))],
body = Fix (Let {bindings = [(Identifier "y",Fix (Number 2))], 
body = Fix (Unary (Fix (Number 3)) NegOp)})})) NegOp)
>

Вот мой полный рабочий пример:

{-# LANGUAGE DeriveFunctor #-}
{-# OPTIONS_GHC -Wall #-}

import Data.Generics.Fixplate

data Identifier = Identifier String deriving (Show)
data PLabel = PLabel deriving (Show)
data Operator = NegOp deriving (Show)

data ProgramF a
  = Unary a
          Operator
  | Number Int
  | Let { bindings :: [(Identifier, a)]
        , body :: a }
  deriving (Show, Functor)
instance ShowF ProgramF where showsPrecF = showsPrec

type Program = Mu ProgramF
type LabelProgram = Attr ProgramF PLabel

splitBindings :: Attr ProgramF a -> Attr ProgramF a
splitBindings (Fix (Ann a (Let (x:y:xs) e)))
  = Fix (Ann a (Let [x] (splitBindings (Fix (Ann a (Let (y:xs) e))))))
splitBindings (Fix e) = Fix (fmap splitBindings e)

toU :: Functor f => Mu f -> Attr f ()
toU = synthetise (const ())

fromU :: Functor f => Attr f () -> Mu f
fromU = forget

mapU :: (Functor f) => (Attr f () -> Attr f ()) -> Mu f -> Mu f
mapU f = fromU . f . toU

testprog :: Program
testprog = Fix $ Unary (Fix $ Let [(Identifier "x", Fix $ Number 1), 
                                   (Identifier "y", Fix $ Number 2)] 
                                  (Fix $ Unary (Fix $ Number 3) NegOp))
                       NegOp

main :: IO ()
main = print $ mapU splitBindings testprog
person K. A. Buhr    schedule 23.04.2018