Вы не первый, кто сталкивается с этой проблемой, и существует множество различных решений в зависимости от рабочего процесса, вкуса и потребностей.
Вот хороший способ подумать об этом.
1. Изолировать листья вашего АСТ
Под листьями я подразумеваю типы типа loc
или id
, которые не зависят от других типов. Они не должны быть в вашем рекурсивном определении типа и, следовательно, не должны быть.
Более того, у вас, вероятно, будут специальные функции для обработки местоположений и идентификаторов, и использование этих функций близко к определению типа является хорошей практикой. Таким образом, вы можете создать файл ast_loc.ml и ast_id.ml с соответствующими определениями и основными функциями.
Это может показаться небольшим, но на самом деле это поможет сделать ваш код более понятным с дополнительным бонусом освещения ast.ml .
2. При необходимости настройте параметры для типов
Теперь я не рекомендую использовать это широко, так как это делает код труднее для чтения, так как имеет больше косвенных ссылок. Проверьте это:
type 't v = Thing of 't
(* potentially in a different later file *)
type t = Stuff of t v
Используя параметр типа, вы можете отложить использование рекурсивности в определении типа. Обратите внимание, что я не рекомендую использовать его для всей вашей AST, так как это будет поддерживать боль, но если у вас есть некоторые средние узлы, которые ведут себя совершенно независимо от остальных, это может помочь.
Например, их можно часто использовать:
type 'a named = { id : id; v : 'a; }
type 'a located = { loc : loc; v: 'a; }
Этот метод особенно полезен, если он помогает факторизовать ваше определение типа. Но, как я уже говорил, не злоупотребляйте этим! Это легко сделать, но трудно поддерживать.
3. В какой-то момент вам нужно большое жирное рекурсивное определение
На сегодняшний день файл Parsetree
компилятора OCaml содержит 958 строк. Это то, что должно быть. Это сложная древовидная структура, и она должна быть видимой.
Обратите внимание, что файл является просто определением типа. Последующие файлы содержат код для управления этим определением (и обычно не вводят новые типы, которые необходимы вне их модуля).
В некотором смысле, я немного противоречу высказанному мной замечанию о loc
и id
, утверждая, что вы должны разделить определение типа и код, но это другой случай: loc
и id
просты типы, которыми можно манипулировать независимо. symbol
имеет смысл только в пределах вашего определения AST. Кроме того, ничто не мешает вам создать файл symbol.ml , который управляет этой частью AST без указания типа (комментарии - ваши друзья, Merlin - необходимость).
Кроме того, рекурсивные функторы - это не то, что я бы посоветовал, если они вам действительно не нужны.