Проблема с циклическими зависимостями между типами и функциями из разных файлов в F # - PullRequest
3 голосов
/ 23 марта 2011

Мой текущий проект использует AST с 40 различными типами (описанные объединения), и несколько типов из этого AST имеют циклическую зависимость. Типы не такие большие, поэтому я поместил их в один файл и применил конструкцию type ... and ... для взаимозависимых типов.

Теперь я добавляю функции, чтобы сделать некоторые вычисления для каждого элемента в AST. Поскольку существует много функций с несколькими строками кода, чтобы сделать исходный код более понятным для чтения, я разделил эти функции в разных файлах.

Это нормально в случае, когда циклическая зависимость отсутствует, также работает, когда зависимые функции находятся в одном файле - в этом случае я могу использовать let rec function1 ... and function2 ... конструкция

Но в моем случае это не сработает.

Также я неправильно думал, что файлы сигнатур могут помочь мне в этом, но их поведение отличается от C ++ - они используются для определения режима доступа к функциям / типам (внутренний / публичный), также здесь можно добавить заголовок комментария к функциям / типам ...

Единственное возможное решение, которое я вижу, - это переместить все функции в один файл и использовать let rec ... and ... and ... and ... and ... конструкция

Возможно, у кого-то есть другие идеи?

Заранее спасибо.

1 Ответ

6 голосов
/ 23 марта 2011

Как уже упоминалось в комментариях, нет способа разделить функции (или типы) с циклическими зависимостями между несколькими файлами. Файлы подписей полезны в основном для целей документирования, поэтому они не помогут.

Трудно дать какой-то совет, не зная, каковы именно зависимости. Однако может оказаться возможным рефакторинг некоторой части реализации с использованием функций или интерфейсов. Например, если у вас есть:

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

Во всяком случае, это всего лишь некоторые общие приемы. Трудно дать более точный совет, не зная больше о типах, с которыми вы работаете. Если бы вы могли поделиться более подробной информацией, возможно, мы могли бы найти способ избежать некоторых рекурсивных ссылок.

...