Можно ли определить типы, которые зависят друг от друга и определены в отдельных файлах?

Я пытаюсь реализовать библиотеку с расширенными возможностями разбора. Я решил, что буду использовать fsyacc, потому что знал об этом в университете. К сожалению, я столкнулся со следующей проблемой.

Я определил класс для главы моей грамматики (Head) и поместил ее реализацию в один файл. Тогда я определил парсер как:

...
%start head
%type <Head> head
...

Fsyacc генерирует разделенный модуль (Parser). Чтобы добиться успеха, он должен быть скомпилирован в следующем порядке: Head.fsParser.fs

Чтобы сделать эту библиотеку похожей на ту, которую вы можете найти в.NET, я бы хотел добавить статический метод Parse в Head. К сожалению, мне нужно использовать методы из модуля Parser.

Я знаю, что такие зависимости типов могут быть решены с помощью оператора 'и', но это применимо только к типам, определенным в одном файле.

Есть ли другой способ создания типов, которые зависят друг от друга, даже если они находятся в отдельных файлах? Я искал механизм разделения объявления / реализации, как в C/C++, но ничего не смог найти.

4 ответа

Решение

Краткий ответ: нет. В F# 2.0 нет способа создавать взаимно рекурсивные сущности для нескольких файлов. (Это то, что мы планируем рассмотреть в следующей версии языка.)

Вы можете обойти это различными способами, обычно используя точку косвенного обращения и мутации. Например, ваш тип Head может иметь статический метод InitializeParser, который помещает значение функции в изменяемую глобальную переменную, а затем статический метод Parse, определенный в Head, может вызываться через этот изменяемый глобал, и после того, как анализатор фактически определен, он может пойти и вызвать InitializeParser, чтобы вставить значение. (Если это не имеет смысла, я могу объяснить это более подробно.)

Я надеялся, что это возможно. После прочтения ответа Брайана я начал искать правильный обходной путь. Я не хотел заставлять пользователей библиотеки вызывать какие-либо методы инициализации. Поэтому я придумал что-то другое.

Если компилятор не может разрешить зависимости во время компиляции, я могу сделать это самостоятельно во время выполнения. Вот определение моего DepencendciesResolver

module DependenciesResolver = 
    let GetMethod(_type, _method) =
        lazy (
            let a = System.Reflection.Assembly.GetExecutingAssembly()
            let t = a.GetType(_type)
            t.GetMethod(_method)
            )

И пример классов, определенных в отдельных файлах:

A.fs

namespace MutualRecursion
type A() =
    static member _b = DependenciesResolver.GetMethod("MutualRecursion.B", "b")
    static member b() = A._b.Value.Invoke(null, [||])

B.fs

nameespace MutualRecursion
type B =
    static member b() = 
        printf "Called b()"

Порядок компиляции: A.fs B.fs

Разве вы не можете обойти это с 3-м файлом, который компилируется после этих двух и расширяет Head новым методом?

Я бы сделал что-то вроде следующего (что я подозреваю, примерно то, что предлагал Брайан). Обратите внимание, что пользователю не нужно делать хитрую инициализацию - сами типы знают, как "завязать узел".

Head.fs

type IParser =
  abstract Parse : string -> int // or whatever
  ...

type Head() =
  static let mutable parser = Unchecked.defaultof<IParser>
  static member internal SetParser p = parser <- p
  member x.DoSomethingCool() =
    let i = parser.Parse("something to parse")
    ...

Parser.fs

type Parser private () =
  static do
    Head.SetParser (Parser())
  interface IParser with
    member x.Parse s = 0
    ...
Другие вопросы по тегам