Можно ли определить типы, которые зависят друг от друга и определены в отдельных файлах?
Я пытаюсь реализовать библиотеку с расширенными возможностями разбора. Я решил, что буду использовать fsyacc, потому что знал об этом в университете. К сожалению, я столкнулся со следующей проблемой.
Я определил класс для главы моей грамматики (Head) и поместил ее реализацию в один файл. Тогда я определил парсер как:
...
%start head
%type <Head> head
...
Fsyacc генерирует разделенный модуль (Parser). Чтобы добиться успеха, он должен быть скомпилирован в следующем порядке: Head.fs
Parser.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
...