是否可以定义相互依赖的类型并在单独的文件中定义?

发布于 2024-09-28 13:08:10 字数 627 浏览 3 评论 0原文

我正在尝试实现一个具有扩展解析功能的库。我决定使用 fsyacc 因为我从大学就知道它。不幸的是我遇到了以下问题。

我为语法的头部(Head)定义了一个类,并将其实现放在一个文件中。然后我将解析器定义为:

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

Fsyacc 生成分离模块(解析器)。为了成功,必须按以下顺序编译它: Head.fs Parser.fs

为了使这个库类似于您在 .NET 中可以找到的库,我会比如向 Head 添加静态 Parse 方法。不幸的是,我需要使用 Parser 模块中的方法。

我知道这种类型依赖可以用“and”运算符来解决,但它仅适用于一个文件中定义的类型。

是否有其他方法可以创建相互依赖的类型,即使它们位于单独的文件中? 我一直在寻找类似于 C/C++ 中的声明/实现分离机制,但我找不到任何事物。

I am trying to implement a library with extended parsing capabilities. I decided that I will use fsyacc because I knew it from the university. Unfortunately I encountered following problem.

I defined a class for the head of my grammar (Head) and place its implementation in a single file. Then I defined parser as:

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

Fsyacc generates seeparated module (Parser). In order to succeed it has to be compiled in following order: Head.fs Parser.fs

In order to make this library similar to what you can find in .NET I would like to add a static Parse method to Head. Unfortunately I would need to make use of methods from Parser module.

I know that such type dependencies can be solved with 'and' operator but it is only applicable to types defined in one file.

Is there any other way to create types that depend on each other even when they are in separate files? I was looking for declaration/implementation separation mechanism like the one in C/C++ but I couldn't find anything.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

不可一世的女人 2024-10-05 13:08:10

简短的回答:不。 F# 2.0 中无法跨多个文件执行相互递归实体。 (这是我们计划在该语言的下一版本中解决的问题。)

您可以通过多种方式解决这个问题,通常使用间接点和突变点。例如,您的 Head 类型可能有一个静态“InitializeParser”方法,该方法将函数值放入可变全局变量中,然后 Head 中定义的静态 Parse 方法可以通过该可变全局调用,并且在实际定义解析器之后,它可以去调用InitializeParser来插入值。(如果这没有意义,我可以更详细地拼写出来。)

Short answer: no. There's no way to do mutually recursive entities across multiple files in F# 2.0. (This is something we plan to address in the next version of the language.)

You can work around this in a variety of ways, typically using a point of indirection and mutation. For example, your Head type could have a static 'InitializeParser' method that pokes a function value into a mutable global variable, and then the static Parse method defined in Head could call via that mutable global, and after the parser is actually defined, it can go and call InitializeParser to poke the value in. (If that doesn't make sense, I can spell it out in more detail.)

扎心 2024-10-05 13:08:10

我希望这是可能的。阅读布莱恩的回复后,我开始寻找合适的解决方法。我不想强迫库用户调用任何初始化方法。因此我想出了一些不同的东西。

如果编译器无法在编译时解决依赖关系,我可以在运行时自行解决。
这是我的 DependendciesResolver 的定义

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

I was hoping that it is possible. After I read Brian's reply I started looking for a proper workaround. I didn't want to force library users to call any initialization methods. Therefore I came up with something diffrent.

If compiler cannot resolve dependencies at compile-time I can do it on my own at run-time.
Here is definition of my DepencendciesResolver

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

And example of classes defined in separated files:

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()"

Compilation order is: A.fs B.fs

白鸥掠海 2024-10-05 13:08:10

您不能使用第三个文件来解决这个问题,该文件在这两个文件之后编译并使用新方法扩展 Head 吗?

Can't you work around this with a 3rd file which compiles after these two and extends Head with the new method?

晚风撩人 2024-10-05 13:08:10

我会做类似以下的事情(我怀疑这大致就是布莱恩的提议)。请注意,用户不必执行任何棘手的初始化 - 类型本身知道如何“喜结良缘”。

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")
    ...

解析器.fs

type Parser private () =
  static do
    Head.SetParser (Parser())
  interface IParser with
    member x.Parse s = 0
    ...

I'd do something like the following (which I suspect is roughly what Brian was proposing). Note that the user doesn't have to do any tricky initialization - the types themselves know how to "tie the knot".

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
    ...
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文