Я сейчас преподаю себе Haskell; скажем, исключительно ради аргумента, что я пишу компилятор в Haskell. У меня есть AST, определенный с чем-то вроде:Не мутирующие деревья в Haskell
data Node =
Block { contents :: [Node], vars :: Map String Variable }
| VarDecl { name :: String }
| VarAssign { name :: String, value :: Node, var :: Variable }
| VarRef { name :: String, var :: Variable }
| Literal { value :: Int }
Каждый Block
является кадр стека. Я хочу разрешить все ссылки переменных.
В мире с изменяемыми данными, как я хотел бы сделать это:
- ходить след дерева по поддержанию самых последних
Block
ищетVarDecl
узлов; на каждом из них я бы добавилVariable
ближайшемуBlock
. - Прогулка по дереву, поиск
VarAssign
иVarRef
Узлы. Каждый раз, когда я вижу один, я бы посмотрел переменную в цепочке кадров стека и аннотировал узел AST с соответствующимVariable
.
Теперь, когда я делаю работу по дереву, и наткнулся на VarRef
, я точно знаю, какой Variable
на самом деле идет речь.
Конечно, в Haskell мне нужен другой подход из-за того, что дерево не изменчиво. Наивный подход - переписать дерево.
declareVariables Block contents _ = Block {
contents = declareVariables contents,
vars = createVariablesFor (findVariablesInBlock contents) }
declareVariables VarAssign name value var =
VarAssign name (declareVariables value) var
declareVariables Literal i = Literal i
...etc...
findVariablesInBlock VarDecl name = [name]
findVariablesInBlock Block contents _ = []
findVariablesInBlock VarAssign name value _ =
findVariablesInBlock value
...etc...
(Весь код полностью непроверенной и чисто для иллюстративных целей.)
Но это довольно отвратительное; Я в конечном итоге хожу по дереву дважды, один раз, чтобы найти Block
s и один раз, чтобы найти VarDecl
s, и там очень много шаблонов. Плюс, учитывая, что Variable
не изменен, существует ограниченное количество использований для аннотации всех моих узлов с одним в первую очередь. Я не могу с пользой аннотировать Variable
, не переписывая все дерево снова.
Альтернатива A: Я мог бы сделать все изменчивым. Теперь у меня есть дерево STRef
s, и все должно жить внутри монады ST
. Как побочный эффект, мой код пахнет.
Альтернатива B: не пытайтесь хранить все в одной и той же структуре данных. Имейте полностью раздельное хранение структур StackFrame
и Variable
, и создавайте их, когда я иду по дереву, оставляя АСТ нетронутым. За исключением того, что это означает, что я не могу легко отобразить от VarRef
до Variable
, что было целым пунктом упражнения. Я мог бы создать таблицу поиска Data.Map VarRef Variable
... но это тоже ужасно.
Какой хороший способ решения Haskell-идиома решить эту проблему?
ну, вы обычно берете среду (где переменные/значения/... связаны) с вами на каждом (обычно рекурсивном) вызове;) - но на этом есть много учебников по этому поводу – Carsten
Я должен добавить, что я не пытаться _actually_ написать компилятор; это был просто самый простой пример той проблемы, которую я пытаюсь решить. –
все равно то же самое - если вы не хотите идти в государство/читатель-монаду (пока), вы должны просто внести эти зависимости в параметры и передать их - FP 101;) – Carsten