Проблема с циклическими зависимостями между типами и функциями из разных файлов в F#
Мой текущий проект использует AST с 40 различными типами (описанные объединения), и несколько типов из этого AST имеют циклическую зависимость. Типы не такие большие, поэтому я положил их в один файл и применил type ... and ...
конструкция для взаимозависимых типов.
Теперь я добавляю функции, чтобы сделать некоторые вычисления для каждого элемента в AST. Поскольку существует множество функций с несколькими строками кода, чтобы сделать исходный код более понятным для чтения, я разделил эти функции в разных файлах.
Это нормально в случае, когда циклическая зависимость отсутствует, также работает, когда зависимые функции находятся в одном файле - в этом случае я могу использовать let rec function1 ... and function2 ...
строительство
Но это не будет работать в моем случае.
Также я неправильно думал, что файлы сигнатур могут помочь мне в этом, но их поведение отличается от C++ - они используются для определения режима доступа к функциям / типам (внутренний / публичный), также здесь можно добавить заголовок комментария к функциям / типам...
Единственное возможное решение, которое я вижу, - это перенести все функции в один файл и использовать let rec ... and ... and ... and ... and ...
строительство
Возможно, у кого-то есть разные идеи?
Заранее спасибо.
1 ответ
Как упоминалось в комментариях, нет способа разделить функции (или типы) с циклическими зависимостями между несколькими файлами. Файлы подписи полезны в основном для целей документирования, поэтому они не помогут.
Трудно дать совет, не зная, каковы именно зависимости. Однако может оказаться возможным рефакторинг некоторой части реализации с использованием функций или интерфейсов. Например, если у вас есть:
let rec process1 (a:T1) =
match a with
| Leaf -> 0
| T2Thing(b) -> process2 b
and process2 (b:T2) =
match b with
| T1Thing(a) -> process1 a
Вы можете изменить функцию process1
принять вторую функцию в качестве аргумента. Это позволяет разделить реализацию между двумя файлами, потому что они больше не являются взаимно рекурсивными:
// File1.fs
let process1 (a:T1) process2 =
match a with
| Leaf -> 0
| T2Thing(b) -> process2 b
// File2.fs
let rec process2 (b:T2) =
match b with
| T1Thing(a) -> process1 a process2
Если вы можете найти более четкую структуру - например, два блока функций, которые содержат логически связанные функции и нуждаются в доступе друг к другу, то вы также можете определить интерфейс. Это не имеет большого смысла для примера с двумя функциями, но это будет выглядеть так:
type IProcess2 =
abstract Process : T2 -> int
let process1 (a:T1) (process2:IProcess2) =
match a with
| Leaf -> 0
| T2Thing(b) -> process2.Process b
let rec process2 (b:T2) =
let process2i =
{ new IProcess2 with
member x.Process(a) = process2 a }
match b with
| T1Thing(a) ->
process1 a process2i
Во всяком случае, это всего лишь некоторые общие методы. Трудно дать более точный совет, не зная больше о типах, с которыми вы работаете. Если бы вы могли поделиться более подробной информацией, возможно, мы могли бы найти способ избежать некоторых рекурсивных ссылок.