Поддерживает ли F # взаимозависимые классы в отдельных файлах?

Я работаю над IronJS, и один из наших исходных файлов становится очень длинным.

Прямо сейчас, я пытаюсь работать с .NET. Я добавляю метод TryBinaryOperation к Undefined, так что С# может использовать семантику JavaScript значения Undefined.

Однако это вводит зависимость от типа Operators, которая вызывает циклическую зависимость.

Runtime.fs:

type BoxedValue() =
    struct
        // Contains IsUndefined and get_Undefined, referencing the Undefined class, below.

...

and type Undefined() =
    inherit DynamicObject()
    ...
    override x.TryBinaryOperation(binder:BinaryOperationBinder, arg:obj, result:obj byref) : bool =
        // Here, we are referencing BoxedValue, above.
        result <- Operators.add(Und, BoxedValue.Box(arg))
        true

...

Operators.fs:

type Operators =
    ...
    // Here, we are referencing BoxedValue.
    static member add(BoxedValue l, BoxedValue r)
        ...

Итак, у нас есть этот набор зависимостей:
BoxedValue->Undefined, Undefined->BoxedValue, Undefined->Operators, Operators->BoxedValue

В идеале мы хотели бы разбить каждый из них на собственный файл.

Возможно ли, что в F # есть кросс файлы круговых зависимостей?

Ответ 1

Нет прямого способа записи круговых зависимостей между типами, определенными в отдельных файлах (в текущей версии F #). В общем, способ решить проблему состоит в том, чтобы сломать одну из зависимостей и позволить некоторую форму параметризации. Затем вы можете заполнить отверстие, чтобы позже создать кольцевую ссылку.

В вашем примере вы, вероятно, можете легко параметризовать тип Undefined, чтобы в качестве параметра ссылаться на Operators. Если вам нужно больше функций, вы можете использовать интерфейс. Только для одной функции (например, Operators.add) вы можете написать примерно следующее:

and type Undefined() =
    inherit DynamicObject()
    ...
    // To be specified by code defined later 
    // (this can either be a function or an interface implementation)
    static let mutable addition = (fun x y -> failwith "not initialized")
    static member SetAddition(f) = addition <- f

    override x.TryBinaryOperation
            (binder:BinaryOperationBinder, arg:obj, result:obj byref) : bool =
        // Here, we are referencing BoxedValue, above.
        result <- addition(Und, BoxedValue.Box(arg))
        true

Код в Operators.fs обеспечит реализацию:

type Operators =
    ...
    // Static constructor of the `Operators` type
    static do Undefined.SetAddition(Operators.add)
    ....

    // Here, we are referencing BoxedValue.
    static member add(BoxedValue l, BoxedValue r)

Единственная сложность в том, что вам нужно убедиться, что статический конструктор Operators будет вызван до того, как тип Undefined будет использоваться в первый раз. Это зависит от вашего конкретного случая, но обычно есть способ сделать это. (Вероятно, существует некоторый основной тип, который может запускать инициализацию)